@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.
- package/LICENSE +21 -0
- package/README.md +756 -0
- package/dist/ai/checker.js +222 -0
- package/dist/ai/checker.js.map +1 -0
- package/dist/ai/checklist.js +190 -0
- package/dist/ai/checklist.js.map +1 -0
- package/dist/ai/cli-presets.js +27 -0
- package/dist/ai/cli-presets.js.map +1 -0
- package/dist/ai/evidence-view.js +143 -0
- package/dist/ai/evidence-view.js.map +1 -0
- package/dist/ai/local-cli.js +58 -0
- package/dist/ai/local-cli.js.map +1 -0
- package/dist/ai/openai-compatible.js +78 -0
- package/dist/ai/openai-compatible.js.map +1 -0
- package/dist/ai/response-parser.js +105 -0
- package/dist/ai/response-parser.js.map +1 -0
- package/dist/ai/types.js +2 -0
- package/dist/ai/types.js.map +1 -0
- package/dist/analysis/consent-mode.js +225 -0
- package/dist/analysis/consent-mode.js.map +1 -0
- package/dist/analysis/deployment.js +2658 -0
- package/dist/analysis/deployment.js.map +1 -0
- package/dist/analysis/diagnosis/copy.en.js +303 -0
- package/dist/analysis/diagnosis/copy.en.js.map +1 -0
- package/dist/analysis/diagnosis/diagnose.js +172 -0
- package/dist/analysis/diagnosis/diagnose.js.map +1 -0
- package/dist/analysis/diagnosis/docs.js +118 -0
- package/dist/analysis/diagnosis/docs.js.map +1 -0
- package/dist/analysis/diagnosis/index.js +5 -0
- package/dist/analysis/diagnosis/index.js.map +1 -0
- package/dist/analysis/diagnosis/rules.js +656 -0
- package/dist/analysis/diagnosis/rules.js.map +1 -0
- package/dist/analysis/diagnosis/types.js +2 -0
- package/dist/analysis/diagnosis/types.js.map +1 -0
- package/dist/cli.js +344 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/env.js +100 -0
- package/dist/config/env.js.map +1 -0
- package/dist/config/preflight.js +167 -0
- package/dist/config/preflight.js.map +1 -0
- package/dist/config/user-config.js +79 -0
- package/dist/config/user-config.js.map +1 -0
- package/dist/evidence/collect.js +527 -0
- package/dist/evidence/collect.js.map +1 -0
- package/dist/evidence/consent-accept.js +377 -0
- package/dist/evidence/consent-accept.js.map +1 -0
- package/dist/evidence/consent.js +340 -0
- package/dist/evidence/consent.js.map +1 -0
- package/dist/evidence/constants.js +615 -0
- package/dist/evidence/constants.js.map +1 -0
- package/dist/evidence/globals.js +132 -0
- package/dist/evidence/globals.js.map +1 -0
- package/dist/evidence/gtm-container.js +432 -0
- package/dist/evidence/gtm-container.js.map +1 -0
- package/dist/evidence/heuristic-banner.js +107 -0
- package/dist/evidence/heuristic-banner.js.map +1 -0
- package/dist/evidence/http-probe.js +139 -0
- package/dist/evidence/http-probe.js.map +1 -0
- package/dist/evidence/init-script.js +112 -0
- package/dist/evidence/init-script.js.map +1 -0
- package/dist/evidence/network.js +365 -0
- package/dist/evidence/network.js.map +1 -0
- package/dist/evidence/preference-center.js +368 -0
- package/dist/evidence/preference-center.js.map +1 -0
- package/dist/evidence/script.js +425 -0
- package/dist/evidence/script.js.map +1 -0
- package/dist/evidence/second-page.js +332 -0
- package/dist/evidence/second-page.js.map +1 -0
- package/dist/evidence/selector-probe.js +53 -0
- package/dist/evidence/selector-probe.js.map +1 -0
- package/dist/evidence/stealth.js +21 -0
- package/dist/evidence/stealth.js.map +1 -0
- package/dist/evidence/types.js +14 -0
- package/dist/evidence/types.js.map +1 -0
- package/dist/interactive/prompt.js +31 -0
- package/dist/interactive/prompt.js.map +1 -0
- package/dist/mcp/server.js +308 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/summary.js +41 -0
- package/dist/mcp/summary.js.map +1 -0
- package/dist/report/context.js +184 -0
- package/dist/report/context.js.map +1 -0
- package/dist/report/projection.js +125 -0
- package/dist/report/projection.js.map +1 -0
- package/dist/report/quote-check.js +90 -0
- package/dist/report/quote-check.js.map +1 -0
- package/dist/report/render.js +178 -0
- package/dist/report/render.js.map +1 -0
- package/dist/report/serializers.js +124 -0
- package/dist/report/serializers.js.map +1 -0
- package/dist/report/store.js +139 -0
- package/dist/report/store.js.map +1 -0
- package/dist/run/verify.js +77 -0
- package/dist/run/verify.js.map +1 -0
- package/dist/utils/duration.js +10 -0
- package/dist/utils/duration.js.map +1 -0
- package/dist/utils/errors.js +17 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/logger.js +59 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/runTimingsLog.js +27 -0
- package/dist/utils/runTimingsLog.js.map +1 -0
- package/dist/utils/slug.js +27 -0
- package/dist/utils/slug.js.map +1 -0
- package/dist/version.js +10 -0
- package/dist/version.js.map +1 -0
- package/package.json +57 -0
- package/scripts/install.sh +314 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { CHECKS_BEARING_ON, CHECK_DECIDED_ITEMS, DEPLOYMENT_SHAPE_GUIDANCE, formatChecklistForPrompt, } from "./checklist.js";
|
|
2
|
+
import { buildPromptPayload } from "./evidence-view.js";
|
|
3
|
+
import { callLocalCli } from "./local-cli.js";
|
|
4
|
+
import { callOpenAICompatible } from "./openai-compatible.js";
|
|
5
|
+
import { parseAIResponse } from "./response-parser.js";
|
|
6
|
+
/**
|
|
7
|
+
* Exported so a caller can measure what a run will actually send. The context
|
|
8
|
+
* budget is the system prompt plus the evidence view, not the evidence alone,
|
|
9
|
+
* and a count that leaves half of it out is the kind of reassurance that ends
|
|
10
|
+
* in a silently truncated payload.
|
|
11
|
+
*/
|
|
12
|
+
export const SYSTEM_PROMPT = `You are an expert reviewer of Dreamdata Analytics Script installations.
|
|
13
|
+
You will be given evidence collected by a headless browser from a customer's website across up to
|
|
14
|
+
three consent states (network requests, JS globals, cookies, consent state, console errors), plus a
|
|
15
|
+
deterministic analysis of the deployment.
|
|
16
|
+
|
|
17
|
+
${DEPLOYMENT_SHAPE_GUIDANCE}
|
|
18
|
+
|
|
19
|
+
Judge the evidence against this checklist:
|
|
20
|
+
${formatChecklistForPrompt()}
|
|
21
|
+
|
|
22
|
+
For each checklist item, decide:
|
|
23
|
+
- true (passed), false (failed), "not_applicable" (the deployment shape makes the item moot), or "inconclusive" (the harness could not collect evidence either way).
|
|
24
|
+
|
|
25
|
+
Evidence rules you must follow:
|
|
26
|
+
- Cite only field paths that actually appear in the evidence JSON you were given, and prefix a per-phase path with its phase (e.g. "grant.network.normalRequestCount"). Never invent a field name or a value. If you want to report something the evidence does not contain, say that it was not collected.
|
|
27
|
+
- Quote the real value you read, so the reader can check it against the JSON.
|
|
28
|
+
- Never contradict deployment.shape or a resolved deployment.checks entry.
|
|
29
|
+
- Describe consent states by their provenance, not their effect alone (see the consent-mode item).
|
|
30
|
+
|
|
31
|
+
Respond with ONLY a JSON object, no prose outside it, in this exact shape:
|
|
32
|
+
{
|
|
33
|
+
"verdicts": [
|
|
34
|
+
{ "id": "script-presence", "passed": true, "evidenceQuote": "short quote or field path from the evidence", "feedback": "one or two sentences" },
|
|
35
|
+
...one entry per checklist item, using the exact ids given above...
|
|
36
|
+
],
|
|
37
|
+
"remediationAdvice": "concrete next steps for the items that genuinely failed, plus anything a human should verify manually; empty string if nothing needs action",
|
|
38
|
+
"unexplainedObservation": "OMIT THIS KEY unless the evidence section explicitly asks for it"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
About "unexplainedObservation": the root causes behind a run's failing checks are decided in code, not by you, and so are the documentation links that go with them. You are asked for this field only when a check failed that no catalogued cause accounts for, and the evidence will name those checks when that happens. Then, and only then, write ONE plain sentence describing what you see in that check's evidence and why the catalogue may not cover it. Never include a URL, a link, a documentation path or a file path in it, and never propose a fix — a link you compose does not exist, and the field is dropped entirely if it contains one. If you have nothing to add, omit the key.`;
|
|
42
|
+
export async function checkWithAI(evidence, analysis, backend) {
|
|
43
|
+
const userPrompt = buildPromptPayload(evidence, analysis);
|
|
44
|
+
const rawResponse = backend.type === "openai"
|
|
45
|
+
? await callOpenAICompatible(backend, SYSTEM_PROMPT, userPrompt)
|
|
46
|
+
: await callLocalCli(backend, `${SYSTEM_PROMPT}\n\nEvidence:\n${userPrompt}`);
|
|
47
|
+
return applyDeterministicChecks(parseAIResponse(rawResponse), analysis);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The deterministic checks outrank the model.
|
|
51
|
+
*
|
|
52
|
+
* The model is asked to respect a failed cross-phase check, but "asked" is
|
|
53
|
+
* not "guaranteed" — and the exit code should not depend on a language
|
|
54
|
+
* model agreeing with arithmetic we already did. A failed check sinks the
|
|
55
|
+
* run regardless of what came back.
|
|
56
|
+
*
|
|
57
|
+
* A page the browser was never served outranks BOTH, and in the other
|
|
58
|
+
* direction: there is nothing to fail. Every item the model scored was scored
|
|
59
|
+
* against a bot wall's challenge document, and the one thing a report must
|
|
60
|
+
* never do is turn that into "no tracking script is installed — install it",
|
|
61
|
+
* which is what zoominfo.com's report said about a site running both bundles
|
|
62
|
+
* out of GTM. The prompt already asks the model not to; this makes it
|
|
63
|
+
* structural, because a customer-facing false negative is not something to
|
|
64
|
+
* leave to a model following instructions.
|
|
65
|
+
*/
|
|
66
|
+
export function applyDeterministicChecks(result, analysis) {
|
|
67
|
+
if (analysis.pageReadability.unreadable)
|
|
68
|
+
return voidVerdicts(result, analysis);
|
|
69
|
+
if (analysis.shape === "third-party-segment-undetermined")
|
|
70
|
+
return voidVerdictsForThirdPartySegment(result, analysis);
|
|
71
|
+
const verdicts = reconcileVerdictsWithChecks(result.verdicts, analysis.checks);
|
|
72
|
+
// Only "fail" sinks the run. A "warn" is a configuration defect the run
|
|
73
|
+
// showed is currently held in check by something else (see CrossPhaseCheck)
|
|
74
|
+
// — reporting an install whose observed behaviour is correct in every phase
|
|
75
|
+
// as failing is how a report stops being believed.
|
|
76
|
+
const anyCheckFailed = analysis.checks.some((check) => check.status === "fail");
|
|
77
|
+
// Recomputed from the reconciled verdicts rather than carried over from the
|
|
78
|
+
// model's, using the same rule the parser applies: a corrected verdict that
|
|
79
|
+
// left overallPass describing the uncorrected one would just move the
|
|
80
|
+
// contradiction somewhere else in the same report.
|
|
81
|
+
const overallPass = !anyCheckFailed && verdicts.some((v) => v.passed === true) && verdicts.every((v) => v.passed !== false);
|
|
82
|
+
// The structural half of the model's contract: on a run where every failing
|
|
83
|
+
// check already has a catalogued cause, the note is dropped whatever came
|
|
84
|
+
// back. Asking the model to omit it is not the same as it being absent.
|
|
85
|
+
const unexplainedObservation = analysis.diagnosis.unexplainedChecks.length > 0 ? result.unexplainedObservation : undefined;
|
|
86
|
+
return { ...result, verdicts, overallPass, unexplainedObservation };
|
|
87
|
+
}
|
|
88
|
+
/** What the checks bearing on an item say on their own, or null when none bear on it. */
|
|
89
|
+
function resolutionFromChecks(checks) {
|
|
90
|
+
if (checks.length === 0)
|
|
91
|
+
return null;
|
|
92
|
+
if (checks.some((check) => check.status === "fail"))
|
|
93
|
+
return false;
|
|
94
|
+
// "warn" sits with "pass" here by the same doctrine as above: the run proved
|
|
95
|
+
// the observed behaviour was correct, and the misconfiguration belongs in
|
|
96
|
+
// the explanation.
|
|
97
|
+
if (checks.some((check) => check.status === "pass" || check.status === "warn"))
|
|
98
|
+
return true;
|
|
99
|
+
if (checks.some((check) => check.status === "inconclusive"))
|
|
100
|
+
return "inconclusive";
|
|
101
|
+
return "not_applicable";
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Brings every verdict into line with the checks that decide it.
|
|
105
|
+
*
|
|
106
|
+
* Sinking overallPass was not enough. The model is told a checklist item whose
|
|
107
|
+
* checks include a failure MUST be scored false, and on nice_com a local model
|
|
108
|
+
* scored consent-mode PASS anyway — "Consent Mode v2 is correctly reflecting
|
|
109
|
+
* the decision in all phases" printed beside clean-before-consent,
|
|
110
|
+
* reject-blocks-normal and second-page-honours-consent all resolved to fail.
|
|
111
|
+
* The run's exit code was right and the page a customer reads said the
|
|
112
|
+
* opposite, in the one direction a verification report must never be wrong in.
|
|
113
|
+
*
|
|
114
|
+
* The model's own words go with it. They are not merely unsupported at that
|
|
115
|
+
* point — they assert the opposite of something already established, so
|
|
116
|
+
* keeping them as commentary beside a corrected verdict would leave the
|
|
117
|
+
* contradiction on the page in smaller type.
|
|
118
|
+
*
|
|
119
|
+
* "warn" is deliberately not here: it means the configuration is wrong and
|
|
120
|
+
* this run proved something else held the line, which is an explanation, not a
|
|
121
|
+
* failure (see CrossPhaseCheck).
|
|
122
|
+
*/
|
|
123
|
+
function reconcileVerdictsWithChecks(verdicts, allChecks) {
|
|
124
|
+
const byId = new Map(allChecks.map((check) => [check.id, check]));
|
|
125
|
+
return verdicts.map((verdict) => {
|
|
126
|
+
const bearing = (CHECKS_BEARING_ON[verdict.id] ?? [])
|
|
127
|
+
.map((id) => byId.get(id))
|
|
128
|
+
.filter((check) => check !== undefined);
|
|
129
|
+
const resolution = resolutionFromChecks(bearing);
|
|
130
|
+
if (resolution === null || resolution === verdict.passed)
|
|
131
|
+
return verdict;
|
|
132
|
+
const failed = bearing.filter((check) => check.status === "fail");
|
|
133
|
+
if (failed.length > 0) {
|
|
134
|
+
return {
|
|
135
|
+
...verdict,
|
|
136
|
+
passed: false,
|
|
137
|
+
evidenceQuote: failed.map((check) => `deployment.checks[${check.id}].status: fail`).join(", "),
|
|
138
|
+
feedback: failed.map((check) => `${check.title}: ${check.detail}`).join(" "),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
// The mirror case, and the narrower one. Nothing failed, and the model
|
|
142
|
+
// scored the item as though something had — on nice_com it returned
|
|
143
|
+
// event-behavior false citing "second-page-keeps-tracking failed" while
|
|
144
|
+
// that check was resolved not_applicable and the other two passed, which
|
|
145
|
+
// reads on the page as "tracking does not survive a page change" about a
|
|
146
|
+
// site where it does. Only the items whose checks decide them outright are
|
|
147
|
+
// corrected this way; see CHECK_DECIDED_ITEMS for why the others are not.
|
|
148
|
+
if (!CHECK_DECIDED_ITEMS.has(verdict.id))
|
|
149
|
+
return verdict;
|
|
150
|
+
return {
|
|
151
|
+
...verdict,
|
|
152
|
+
passed: resolution,
|
|
153
|
+
evidenceQuote: bearing.map((check) => `deployment.checks[${check.id}].status: ${check.status}`).join(", "),
|
|
154
|
+
feedback: bearing.map((check) => `${check.title}: ${check.detail}`).join(" "),
|
|
155
|
+
};
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Rewrites every verdict as "not observed" for a run that found a real
|
|
160
|
+
* Segment install instead of a Dreamdata bundle — see thirdPartySegmentSignals.
|
|
161
|
+
*
|
|
162
|
+
* Same reasoning as voidVerdicts: whether Dreamdata receives these events at
|
|
163
|
+
* all is decided by a server-side Segment destination this harness cannot
|
|
164
|
+
* see, so no verdict this run reaches (pass OR fail) is trustworthy, and the
|
|
165
|
+
* exit code should not depend on a model correctly following the "say
|
|
166
|
+
* inconclusive, not 'install the script'" instruction in the prompt.
|
|
167
|
+
*/
|
|
168
|
+
function voidVerdictsForThirdPartySegment(result, analysis) {
|
|
169
|
+
return {
|
|
170
|
+
...result,
|
|
171
|
+
verdicts: result.verdicts.map((verdict) => ({
|
|
172
|
+
...verdict,
|
|
173
|
+
passed: "inconclusive",
|
|
174
|
+
evidenceQuote: "deployment.thirdPartySegmentSignals",
|
|
175
|
+
feedback: "Not observed: a real Segment install was found on this page instead of a Dreamdata bundle. Whether Dreamdata receives these events depends on a server-side Segment destination this harness cannot see, so nothing here confirms or denies delivery.",
|
|
176
|
+
})),
|
|
177
|
+
overallPass: false,
|
|
178
|
+
remediationAdvice: `${analysis.shapeReason} This run cannot confirm or deny event delivery from the browser alone — check Segment's own source/destination configuration, or Dreamdata's ingestion records, directly.`,
|
|
179
|
+
// A note about somebody else's analytics install is not a finding about
|
|
180
|
+
// this site, by the same argument that voids the verdicts above.
|
|
181
|
+
unexplainedObservation: undefined,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Rewrites every verdict as "not observed" and replaces the remediation with
|
|
186
|
+
* the only action this run supports: look again, by hand or headed.
|
|
187
|
+
*
|
|
188
|
+
* `overallPass` stays false because the run confirmed nothing — but the
|
|
189
|
+
* renderers read `pageReadability` and print INCONCLUSIVE rather than FAIL, so
|
|
190
|
+
* a blocked run does not read as a broken install. A verdict the model scored
|
|
191
|
+
* `true` goes too: a pass earned against a challenge page is as wrong as a
|
|
192
|
+
* failure, and quietly keeping the passes would be the more misleading half.
|
|
193
|
+
*/
|
|
194
|
+
function voidVerdicts(result, analysis) {
|
|
195
|
+
// pageReadability.reason already carries what happened and what to do about
|
|
196
|
+
// it, so the advice states it once and then adds only what it does not: the
|
|
197
|
+
// concrete install, in the terms an account manager needs. Appending
|
|
198
|
+
// shapeReason here as well was three overlapping paragraphs saying it twice.
|
|
199
|
+
const installed = [
|
|
200
|
+
...analysis.normalBundleSignals.map((signal) => signal.detail),
|
|
201
|
+
...(analysis.cookielessBundleConfigured ? ["a cookieless bundle is part of the deployment too"] : []),
|
|
202
|
+
];
|
|
203
|
+
const installEvidence = installed.length > 0
|
|
204
|
+
? ` Recovered install (${analysis.shape}), which is what this run CAN still say: ${installed.join("; ")}.`
|
|
205
|
+
: " Not even the install could be recovered: the served markup names no Dreamdata tag, and no tag-manager container it names ships one.";
|
|
206
|
+
return {
|
|
207
|
+
...result,
|
|
208
|
+
verdicts: result.verdicts.map((verdict) => ({
|
|
209
|
+
...verdict,
|
|
210
|
+
passed: "inconclusive",
|
|
211
|
+
evidenceQuote: "deployment.pageReadability.unreadable: true",
|
|
212
|
+
feedback: "Not observed: the browser was never served this site's page, so there was no page to judge this on. See deployment.pageReadability, and the remediation advice for what to do next.",
|
|
213
|
+
})),
|
|
214
|
+
overallPass: false,
|
|
215
|
+
remediationAdvice: "Nothing about this site's tracking was verified, and nothing here should be reported to the account as a finding. " +
|
|
216
|
+
`${analysis.pageReadability.reason}${installEvidence}`,
|
|
217
|
+
// The model's note would be about a bot wall's challenge document, which
|
|
218
|
+
// is exactly what "nothing here should be reported as a finding" covers.
|
|
219
|
+
unexplainedObservation: undefined,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
//# sourceMappingURL=checker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checker.js","sourceRoot":"","sources":["../../src/ai/checker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which cross-phase checks decide which checklist item.
|
|
3
|
+
*
|
|
4
|
+
* This lived only as a paragraph inside the prompt below, which made it a
|
|
5
|
+
* rule the model was asked to follow and nothing could enforce — and on
|
|
6
|
+
* nice_com a local model scored consent-mode PASS with three of its six
|
|
7
|
+
* checks resolved to fail, producing a report that contradicted itself in the
|
|
8
|
+
* direction of "no problem here". It is data now: the prompt paragraph is
|
|
9
|
+
* generated from it (so the two cannot drift), and applyDeterministicChecks
|
|
10
|
+
* enforces it (so following it is not optional).
|
|
11
|
+
*
|
|
12
|
+
* Every item is now covered by at least one check. The two that used to be
|
|
13
|
+
* the model's alone — script-presence and debugging-signals — were the gap
|
|
14
|
+
* this map's own doctrine argued against: localazy.com had nothing installed
|
|
15
|
+
* on a perfectly readable page, and because no check bore on script-presence,
|
|
16
|
+
* the run failed only because the model happened to say so. Had it scored
|
|
17
|
+
* that item "pass", nothing would have overridden it.
|
|
18
|
+
*/
|
|
19
|
+
export const CHECKS_BEARING_ON = {
|
|
20
|
+
"script-presence": ["script-installed"],
|
|
21
|
+
"consent-mode": [
|
|
22
|
+
"clean-before-consent",
|
|
23
|
+
"clean-consent-mode-default",
|
|
24
|
+
"grant-enables-normal",
|
|
25
|
+
"reject-blocks-normal",
|
|
26
|
+
"consent-mode-signal",
|
|
27
|
+
"consent-mode-v2-enabled",
|
|
28
|
+
"second-page-honours-consent",
|
|
29
|
+
],
|
|
30
|
+
cookies: [
|
|
31
|
+
"grant-sets-identity-cookies",
|
|
32
|
+
"reject-writes-no-identity",
|
|
33
|
+
"second-page-honours-consent",
|
|
34
|
+
"anonymous-id-stable",
|
|
35
|
+
],
|
|
36
|
+
"core-tracking": [
|
|
37
|
+
"cookieless-covers-unconsented",
|
|
38
|
+
"request-payload-complete",
|
|
39
|
+
"request-delivery-accepted",
|
|
40
|
+
"first-party-delivery-host",
|
|
41
|
+
],
|
|
42
|
+
"event-behavior": ["page-view-counted-once", "second-page-keeps-tracking", "soft-nav-keeps-tracking"],
|
|
43
|
+
"global-object": ["global-handoff"],
|
|
44
|
+
"debugging-signals": ["console-clean", "request-delivery-accepted", "collection-complete"],
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* The items whose checks decide them outright, so a verdict that disagrees
|
|
48
|
+
* with those checks is wrong in either direction.
|
|
49
|
+
*
|
|
50
|
+
* Their descriptions say so in as many words: consent-mode is scored "from
|
|
51
|
+
* the five consent checks named in the preamble — they are already resolved
|
|
52
|
+
* and they, not your own reading, decide it"; global-object's says "THE
|
|
53
|
+
* global-handoff CHECK ALREADY DECIDES THIS"; event-behavior's says "BOTH are
|
|
54
|
+
* already resolved by checks; your job is to explain them, not to re-count".
|
|
55
|
+
*
|
|
56
|
+
* script-presence joins them because script-installed decides the whole of it
|
|
57
|
+
* and nothing else: a bundle belongs to this deployment, and it names an
|
|
58
|
+
* account. Both halves are read from facts computed across every phase
|
|
59
|
+
* (deployment.shape, install.writeKey) rather than from any one phase's
|
|
60
|
+
* script.present, which is the only reading that was ever ambiguous.
|
|
61
|
+
*
|
|
62
|
+
* debugging-signals joins them now that collection-complete covers the half
|
|
63
|
+
* console-clean and request-delivery-accepted do not. What was left after
|
|
64
|
+
* that is debug-mode confirmability, and the harness never enables debug mode
|
|
65
|
+
* — so it is never something a run can score the item DOWN for, only
|
|
66
|
+
* something it cannot confirm. blackline.com is what forced this: its Nuxt
|
|
67
|
+
* plugin prints "Plugin loaded - YOU SHOULD SEE THIS IN PRODUCTION" through
|
|
68
|
+
* console.error, and qa.com's install reports a third-party destination it
|
|
69
|
+
* could not enable; console-clean grades both warnings (it never fails — see
|
|
70
|
+
* buildConsoleCheck), and the model failed the item anyway on the raw
|
|
71
|
+
* console.errors list, sinking two runs whose every event was delivered and
|
|
72
|
+
* answered 2xx.
|
|
73
|
+
*
|
|
74
|
+
* cookies and core-tracking stay out, and the reason is the same for each:
|
|
75
|
+
* membership here forces a PASS over a model's FAIL, so an item belongs only
|
|
76
|
+
* when its checks cover everything its description claims.
|
|
77
|
+
* - core-tracking: its checks resolve payload shape and delivery, but the
|
|
78
|
+
* central question — did a page view fire at all in each phase a bundle was
|
|
79
|
+
* expected to run — is still spread across the consent checks rather than
|
|
80
|
+
* owned by one.
|
|
81
|
+
* - cookies: anonymous-id stability is covered now, but the per-name storage
|
|
82
|
+
* expectations the description lists are not.
|
|
83
|
+
*/
|
|
84
|
+
export const CHECK_DECIDED_ITEMS = new Set([
|
|
85
|
+
"consent-mode",
|
|
86
|
+
"global-object",
|
|
87
|
+
"event-behavior",
|
|
88
|
+
"script-presence",
|
|
89
|
+
"debugging-signals",
|
|
90
|
+
]);
|
|
91
|
+
/** The prompt's "which checks bear on which item" list, rendered from the map above. */
|
|
92
|
+
function formatChecksBearingOnItems() {
|
|
93
|
+
return Object.entries(CHECKS_BEARING_ON)
|
|
94
|
+
.map(([item, checks]) => `- ${item}: ${checks.join(", ")}`)
|
|
95
|
+
.join("\n");
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Preamble shared by every item.
|
|
99
|
+
*
|
|
100
|
+
* The deployment shape and the cross-phase consent checks used to be inferred
|
|
101
|
+
* by the model from a page of prose rules. They are deterministic functions
|
|
102
|
+
* of the evidence, so they are now computed in code and handed over as facts;
|
|
103
|
+
* this preamble's job is to stop the model re-deriving (and contradicting)
|
|
104
|
+
* them.
|
|
105
|
+
*/
|
|
106
|
+
export const DEPLOYMENT_SHAPE_GUIDANCE = `The evidence covers up to three separate visits to the same site, each in its own fresh browser context:
|
|
107
|
+
|
|
108
|
+
- "clean": loaded and left alone. Whatever the site does before the visitor answers the banner.
|
|
109
|
+
- "grant": the consent banner's accept control was driven, then the page was reloaded.
|
|
110
|
+
- "reject": the banner's reject/decline control was driven, then the page was reloaded.
|
|
111
|
+
|
|
112
|
+
Any phase may be missing (deployment.phasesMissing lists which). A verdict that would depend on a missing phase is "inconclusive", never a failure.
|
|
113
|
+
|
|
114
|
+
EACH PHASE COVERS TWO PAGE VIEWS, and they are reported separately. Every field at the top of a phase — network, cookies, globals, script, consent — describes the FIRST page only. The phase then followed one internal link and recorded what tracked on the page it landed on under phase.secondPage, whose network/cookies fields have the same meaning for that second page. The two are kept apart because the failure they expose only exists in the gap between them: an install that fires correctly on the entry page and then goes silent looks perfect in every first-page field.
|
|
115
|
+
|
|
116
|
+
- secondPage.kind "hard" means a new document was loaded, so every bundle got a fresh chance to run. "soft" means the site's own router handled it inside the same document — no script re-ran, so a bundle that only tracks on load can never fire again, and the site's code has to call page() on route changes itself.
|
|
117
|
+
- secondPage null (probe disabled), attempted false, or urlChanged false all mean no second page was observed. That is "we could not look", never a failure.
|
|
118
|
+
|
|
119
|
+
BEFORE ANY OF THAT, ONE PREREQUISITE. deployment.pageReadability answers "was the browser ever served this site's page?" — because a bot wall does not refuse an automated browser, it answers with a challenge document instead (a press-and-hold control, a "verify you are human" page). A phase collected from that document has no script tag, no tag-manager container, no consent banner, no request and no internal link to walk to, which is field-for-field identical to a site with nothing installed. When deployment.pageReadability.unreadable is true, NO phase field is evidence about this site: score EVERY item "inconclusive", say plainly that the browser was turned away and quote deployment.pageReadability.reason, and put nothing in remediationAdvice except "re-run headed, then verify by hand". Never write "no tracking script is installed" or "install the script" on such a run — the install may be perfectly healthy and simply unobserved. deployment.shape and deployment.normalBundleSignals may still name a real install there: those come from the markup the site serves a plain HTTP GET (which carries no browser fingerprint, so the wall lets it through) and from the tag-manager container that markup names, and neither depends on the browser having rendered anything. Report that install as what is INSTALLED, and its behaviour as unobserved.
|
|
120
|
+
|
|
121
|
+
TWO PARTS OF THE EVIDENCE ARE ALREADY DECIDED. Do not re-derive them, and never contradict them:
|
|
122
|
+
|
|
123
|
+
1. deployment.shape — the install's shape, computed from all phases at once:
|
|
124
|
+
- "consent-gated-dual": both a normal (cookie-based) and a cookieless bundle belong to this deployment. Which one runs should follow the consent choice.
|
|
125
|
+
- "cookieless-by-design": ONLY a cookieless bundle exists. This is asserted only when no normal-bundle tag, tag-manager tag, CMP-blocked tag, loader companion reference and no normal request were found in ANY phase. If deployment.shape is not this value, a normal bundle IS part of the install even when script.normalSnippet is false in a given phase — a consent-gated or tag-manager-injected bundle is absent from the DOM by design. deployment.normalBundleSignals says exactly why we know.
|
|
126
|
+
- "normal-only": only the standard cookie-based bundle is installed.
|
|
127
|
+
- "helper-only" / "nothing-installed": no tracking bundle at all — a real failure. NOT available to a run where deployment.pageReadability.unreadable is true; check that first.
|
|
128
|
+
- "page-unreadable": the browser was never served this site's page and nothing could be recovered from the served markup either. Score every item "inconclusive"; this is not a statement about the site.
|
|
129
|
+
- "undetermined": a consent banner was detected but this run could not successfully drive it (grant/reject) in any phase, so whether a normal bundle exists cannot be determined from this evidence — it may be genuinely cookieless-by-design, or a normal bundle may simply be sitting behind a banner this harness could not click. NEVER score a checklist item false because of this shape. Score the affected items "inconclusive" and say plainly, quoting deployment.shapeReason, that the banner needs a manual click-through.
|
|
130
|
+
- "third-party-segment-undetermined": would otherwise read "nothing-installed" or "helper-only", but a REAL Segment install (analytics.js/analytics-next, not Dreamdata's segment-compat loader) was found instead — deployment.thirdPartySegmentSignals says exactly what (a <script src> on cdn.segment.com, and/or a request to api.segment.io). This is a customer whose connection to Dreamdata, if it exists at all, is a server-side Segment destination/connection configured in Segment's own dashboard — that hop happens entirely outside the browser, so this harness can neither confirm nor deny that Dreamdata receives anything. Every item is answered exactly like "undetermined": score them "inconclusive" (this is enforced in code regardless of what you say here), and NEVER write "no tracking script is installed" or "install the script" — a tracking script IS installed, just not Dreamdata's, and the right next step is checking Segment's destination configuration or Dreamdata's own ingestion records directly, not re-installing anything on the page.
|
|
131
|
+
deployment.segmentCompat true means the install exposes window.analytics and ajs_* storage instead of window.dreamdata and dd_*; judge those fields instead, it is a valid shape.
|
|
132
|
+
deployment.consentGateUndetermined can also be true alongside a DIFFERENT concluded shape (e.g. "consent-gated-dual" reached from a tag-manager container or a blocked DOM tag despite the drive failing) — in that case the shape itself still stands, but any check resting on the grant/reject phase's outcome is already "inconclusive" for the same reason; do not re-litigate it.
|
|
133
|
+
|
|
134
|
+
2. deployment.checks — the cross-phase consent checks, each already resolved to pass / fail / inconclusive / not_applicable with its own evidence. These are the authority on consent behaviour. A checklist item whose subject includes a check with status "fail" MUST be scored false, quoting that check's evidence. A check that is "inconclusive" or "not_applicable" cannot on its own make an item pass. A check with status "warn" MUST NOT make an item false on its own: it means the configuration is wrong but this run proved something else held the line and the observed behaviour was correct, so score the item on the rest of its checks and report the warning in the explanation — say what is misconfigured, what is currently compensating for it, and that nothing leaked in this run. Put its fix in remediationAdvice, worded as hardening rather than as an outage.
|
|
135
|
+
|
|
136
|
+
Which checks bear on which item:
|
|
137
|
+
${formatChecksBearingOnItems()}
|
|
138
|
+
|
|
139
|
+
Every checklist item now has at least one check bearing on it, and the list above says which. Your remaining job is the per-phase mechanics no check covers — whether a page view fired in each phase a bundle was expected to run, how the install is put together, what a payload looks like — plus explaining, in plain language, what the resolved checks mean for this site.
|
|
140
|
+
|
|
141
|
+
Two rules override the wording of any individual item:
|
|
142
|
+
|
|
143
|
+
1. Never score an item as failed for the absence of something the shape makes inapplicable. Use "not_applicable" and name the shape that drove it.
|
|
144
|
+
2. Never score an item as failed for evidence this harness cannot collect. Each page view is a LOAD or a followed link: no form submission, no clicks other than the consent banner and that one link. Anything requiring those is "inconclusive", not failed.
|
|
145
|
+
|
|
146
|
+
Distinguish "correct behaviour we observed" from "behaviour we could not observe" from "actually broken". Only the third is a failure.`;
|
|
147
|
+
export const CORRECTNESS_CHECKLIST = [
|
|
148
|
+
{
|
|
149
|
+
id: "script-presence",
|
|
150
|
+
title: "Script presence & validity",
|
|
151
|
+
description: "A Dreamdata tracking bundle is part of the deployment and a writeKey is configured. THE script-installed CHECK ALREADY DECIDES THIS — it reads deployment.shape (a union across every phase) rather than any one phase's script.present, and install.writeKey, which falls back to the key the requests carried. Score the item from its status and explain what it found. The rest of this item is the vocabulary for that explanation. Judge across phases: script.present false in the clean phase but true in the grant phase is a consent-gated install, not a missing one. script.writeKey is extracted from the matched snippet only, so null means it was not in the src path or the snippet — check network.observedWriteKeys before calling it a failure. script.helperScriptOnly true in every phase is a genuine failure: the only Dreamdata script found was a helper (identify-form), not a tracking bundle. script.matches lists every tag found, with blockedBy naming the CMP that neutralised it — a blocked tag is installed, not absent. deployment.install.method 'bundled-no-global' is a fully valid install with no snippet to find at all: the SDK is imported as a package and lives in the site's own bundle, so script.present false, script.matches empty and script.writeKey null in EVERY phase are expected there, and network.observedWriteKeys is where the key shows up. Score it present and say how it is installed.",
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
id: "core-tracking",
|
|
155
|
+
title: "Core tracking",
|
|
156
|
+
description: "In each phase where a bundle was expected to run, an automatic page view fired and reached Dreamdata's servers: an endpointName of 'page' or 'batch' with a 2xx status. Judge each payload from its `identity` object, not the raw body. Required fields depend on the request's own `mode`: for 'normal', identity.hasWriteKey AND identity.hasAnonymousId must be true. For 'cookieless', ONLY identity.hasWriteKey is required — cookieless events carry no anonymousId by design, identity is resolved server-side, and reporting its absence as a failure is wrong. identity.hasUserData is optional in both modes. If a phase's network.requests is empty, check network.trackingHosts: requests to any other host were invisible to this run. Requests delivered to the site's OWN domain rather than a Dreamdata one are normal — a first-party CNAME — and the first-party-delivery-host check states which host was recognised and that its writeKey matches this install's; take that as settled and never call such an install misconfigured for it. THREE CHECKS ALREADY RESOLVE MOST OF THIS: request-payload-complete applies the per-mode identity rules above to every request the run captured, request-delivery-accepted reads the RESPONSE (an event answered 4xx left the browser but was never recorded, which every request-side field would report as healthy), and cookieless-covers-unconsented decides whether cookieless coverage of the clean/reject phases is adequate. Do not re-derive any of the three; your remaining job is whether a page view fired at all in each phase where a bundle was expected to run.",
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
id: "global-object",
|
|
160
|
+
title: "Global object",
|
|
161
|
+
description: "Did the bundle that was allowed to load actually finish handing off to its global? THE global-handoff CHECK ALREADY DECIDES THIS. It is resolved in code, per phase, against the global the phase's own requests imply — window.dreamdata for a phase where the normal bundle fired, window['dreamdata-cl'] where the cookieless one did, window.analytics for a segment-compat install — and only in phases where a bundle demonstrably ran, so a gated bundle is never held to a handoff it was never allowed to start. Score the item from its status: fail -> false quoting its detail, pass -> true, not_applicable / inconclusive -> the same. Do not re-derive it from the globals fields, and in particular NEVER fail this item on globals.initialized being false on its own: a real, fully-methoded SDK object whose ready() has already fired can still carry initialized false (nice.com does), which is why the check treats initialized true and ready() as two independent confirmations and neither one's absence as a defect. " +
|
|
162
|
+
"The two states it fails on, for your explanation: globals.dreamdataIsStubQueue true means the global is still the loader's stub ARRAY — the snippet creates it as an array that buffers calls, the bundle replaces it wholesale once it has resolved a write key and loaded — so the handoff never completed and every dreamdata.track/identify call the site's own code makes is pushed into a dead array while the automatic page view still looks fine on the wire. The second is the corroboration the check applies when there is no stub: the normal bundle deletes window['dreamdata-cl'] as the last step of a successful handoff, so normal requests alongside a surviving cookieless global and no confirmation of its own is the same failure. Name whichever cause the console supports — 'Failed to load Write Key' (the snippet was pasted without substituting the key, or a bare script tag was loaded with no snippet in front of it), 'Dreamdata snippet included twice' (two copies, typically one hardcoded and one tag-manager-injected), a CSP violation (the customer's policy blocks the bundle's CDN) — and if console.errors is empty, say the stub array is unexplained by the evidence rather than inventing a cause. " +
|
|
163
|
+
"THE ABSENCE OF A GLOBAL IS NOT A FAILURE BY ITSELF. When deployment.install.method is 'bundled-no-global' the check is not_applicable and you must score the item that way too: requests reached Dreamdata, no tag was in the DOM in any phase, no tag manager shipped one, and NO global — not even a stub — was ever on window, which is what importing the SDK as a package (npm) and holding the instance in module scope looks like. There is no loader, so there is nothing to hand off and no global to find. It costs no events: the site's code calls the instance it imported, and a page event on a soft navigation proves it, since the SDK has no route-change tracking of its own and that call can only have come from a reference the site holds. Say plainly that tracking is healthy and mention only as an advisory that console debugging and any window.dreamdata call from OUTSIDE that bundle (a GTM custom HTML tag, a third-party script) will not find it. " +
|
|
164
|
+
"ONE ABSENCE THAT IS ALWAYS CORRECT: globals.cookielessGlobalExists false in a phase where the normal bundle initialized. The normal bundle deletes window['dreamdata-cl'] the moment it initializes, precisely so only one instance survives — never report that as the cookieless bundle failing. Both globals initialized in the same phase is the opposite problem and belongs to event-behavior. globals.readyCallbackFired refers to globals.readyCallbackGlobal; it only speaks for the global it names."
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
id: "cookies",
|
|
168
|
+
title: "Cookies",
|
|
169
|
+
description: "Identity storage must follow the consent answer in both directions. The grant-sets-identity-cookies, reject-writes-no-identity, second-page-honours-consent and anonymous-id-stable checks already resolve this; score the item from them. Beyond those: for a normal or dual install, dd_anonymous_id, dd_user_id, dd_user_traits, dd_group_id, dd_group_properties in cookies.values (or the ajs_* equivalents in cookies.ajsValues when cookies.usesAjsCompatPrefix is true) are what a running normal bundle should set. Writing no cookies is CORRECT for a cookieless-by-design install and for any phase where consent was withheld. Anonymous-id stability is resolved by anonymous-id-stable, which compares the id across the navigation after normalising the three spellings one value arrives in (JSON-quoted, percent-encoded, bare) — a changed id splits one visitor into two. An id that appears only on the second page of a clean or reject phase is a consent question rather than a continuity one, and second-page-honours-consent is the check that asks it.",
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
id: "consent-mode",
|
|
173
|
+
title: "Google Consent Mode v2",
|
|
174
|
+
description: "WHETHER CONSENT MODE V2 IS ENABLED AT ALL IS DECIDED BY consent-mode-v2-enabled, NOT BY YOU. It reads google_tag_data.ics's own diagnostic flags, which the other checks cannot: ics.active being true is the only proof gtag('consent', …) was ever called, and without it a readable analytics_storage state proves nothing — gtag/GTM creates the consent entries itself as soon as a container loads, so a site with no Consent Mode wiring whatsoever still reports readable signals and a 'denied' or 'unset' analytics_storage. Its detail already names what is wrong (not enabled, running v1, consent set after the tags ran, a permissive global default overridden per region, or a wait_for_update window that closed unanswered); quote it rather than re-deriving it, and never contradict it. One thing it deliberately does NOT answer: whether an unanswered visitor is actually tracked. A site can pass it while every unconsented visit goes untracked, because the standard bundle's loader correctly declines to initialise and no cookieless bundle sits behind it — cookieless-covers-unconsented is the check that asks that. A 'pass' saying no consent update applied during the run is the CORRECT state for a visitor who never answered the banner; do not report it as a fault. Score this item from the five consent checks named in the preamble — they are already resolved and they, not your own reading, decide it. Any of them failing makes this item false; one at 'warn' does not — that is a misconfiguration this run proved was held in check by something else, so it belongs in the explanation and in remediationAdvice, not in the score. If they are all not_applicable (no CMP and no Consent Mode on the page, or a cookieless-by-design install with nothing to gate), the item is 'not_applicable'. If they are all inconclusive (e.g. the banner could not be driven), the item is 'inconclusive'. When explaining, describe consent state by its provenance: consent.analyticsStorageSource 'update' means something explicitly set it — a real signal. 'default' means the site or CMP configured a real default that applies to this visitor — a deliberate design choice, not a user decision. 'implicit-default' means gtag.js's own fallback applied because nothing declared covers this visitor — describe that as 'no consent decision applies to this visitor yet', NEVER as 'the user denied consent', and never as proof Consent Mode is wired up. Neither 'default' nor 'implicit-default' is evidence that a driven choice was carried: only 'update' is. consent.cmpHasResponse false confirms the banner was unanswered in that phase. Also report consentAcceptance.consentSignalConfirmed false where present: the CMP stored the choice but never emitted the Consent Mode update, which leaves every gated tag frozen. The mirror case is just as reportable and is what the consent-mode-signal check now fails on: the update did fire while the banner was being answered (consentSignalConfirmed true) but the phase's own consent.analyticsStorageState ends up on the opposite value after the post-consent reload — the decision does not survive a page load, so the page the visitor actually browses still has everything gated on Consent Mode blocked. consent.cmpVendor 'unknown-custom' (consent.cmpVendorConfirmed false) means a bespoke banner was matched by generic accept/reject-text heuristics, not a recognised CMP — treat it exactly like any other detected CMP for scoring purposes, just note in your explanation that the vendor identity itself is a best-effort guess, not confirmed.",
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
id: "event-behavior",
|
|
178
|
+
title: "Event behavior",
|
|
179
|
+
description: "Two questions: did exactly one bundle track the page view, and does tracking survive the visitor moving off the entry page? BOTH are already resolved by checks; your job is to explain them, not to re-count. EXCLUSIVITY — page-view-counted-once is the authority. It groups page events by the URL they reported WITHIN a single observed page view and fails only when one page view produced more than one, so it, not a phase's raw mode counts, decides this half of the item. It also leaves one page view deliberately unjudged: the entry page of a phase that answered the banner (network.spansConsentAnswer true), because that URL was loaded twice — once unanswered, once with the decision in force — and a consent-gated install is SUPPOSED to track both, anonymously first and identified after. Two page events for that URL there are the consent upgrade the visitor just asked for, NOT a duplicate loader, and you must not call them one; a real duplicate fires on every load and shows on the page views the check does judge. That excuse stops at the build string: two loads are two runs of ONE install, and one install ships every bundle it has at ONE version, so page events naming two different context.library versions (see network.observedLibraries) are two separate COPIES of the script on the same page — an npm build inside the site's own app next to a CDN build a tag manager injects, say — and the check judges those on the answered entry page too. If it fails, quote its detail: it already names which of the four causes this is (the same messageId delivered twice, two different builds of the script running side by side, one request from each bundle, or two page events from one build) and they are fixed in different places — a second copy in particular is REMOVED, not reconfigured, and it hides on soft navigations because only the copy wired to the router fires there. The background it rests on, for your explanation only: the two bundles are built to be mutually exclusive within a page view — the cookieless snippet stands down when it sees the normal tag already present and unblocked, and the normal bundle deletes the cookieless global as soon as it initializes — so one page view should produce requests in ONE mode, and a page view counted twice is once identified and once anonymous. DO NOT derive an exclusivity failure yourself from network.normalRequestCount and network.cookielessRequestCount both being above zero in one phase. Those counts span TWO page views (the entry page and the page the phase walked to), so a cookieless entry page followed by a normal second page adds up to 'both modes fired' while being a consent question rather than a duplicate — second-page-honours-consent asks it. DO NOT judge the ORDER the two modes fired in. network.cookielessRequestSeenFirst is descriptive only: cookieless is the fallback, not the first mover, and in a healthy dual install under granted consent it should not fire at all. One mode being silent in a phase is normal and never a failure on its own. CONTINUITY — second-page-keeps-tracking and soft-nav-keeps-tracking are already resolved; either failing makes this item false. Quote the failing check and name the mechanism, because the two are fixed in different places: a 'hard' navigation that stopped tracking means the snippet (or the tag-manager trigger that injects it) is only on the entry page and needs to cover the rest of the site, while a 'soft' one means the bundle is loaded but nothing told it the route changed — the SDK has NO route-change tracking of any kind, the snippet calls page() exactly once at load, so an in-app navigation only reports if the site's own router calls dreamdata.page(). Across phases, a bundle that fires in clean and stops in reject is correct behaviour, not a regression. Say nothing about form submissions: form tracking is an opt-in load option that is off unless the customer enabled it (and is disabled outright in cookieless mode), so this harness never submitting a form is not a gap in the verdict. Anonymous-id consistency across events is likewise not exercised here; the cookies item covers the part that is observable.",
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
id: "debugging-signals",
|
|
183
|
+
title: "Debugging signals",
|
|
184
|
+
description: "THE console-clean, request-delivery-accepted AND collection-complete CHECKS ALREADY DECIDE THIS: the first on the Dreamdata-specific console errors and warnings, the second on whether the events were answered 2xx, the third on whether anything limited what this run could observe. Score the item from them, not from the raw console.errors list. console-clean is never a failure whatever severity a message was printed at: a console line is the install narrating itself, and anything it would have cost — a bundle that never loaded, events that never fired, events that were rejected — is observed directly by another check. Report the messages, quote them, and say what they mean; do not fail the item for them. console.errors and console.warnings are filtered to messages mentioning 'dreamdata', so an empty list means no Dreamdata-specific error, NOT that the page is error-free — say so rather than implying broader health. Surface any collectionErrors that limit how much the run could see. Debug mode (dreamdata.debug(true) / the DD_DEBUGGER localStorage flag) is never enabled by this harness, so it cannot be confirmed; prefer 'inconclusive' when there is no payload to inspect.",
|
|
185
|
+
},
|
|
186
|
+
];
|
|
187
|
+
export function formatChecklistForPrompt() {
|
|
188
|
+
return CORRECTNESS_CHECKLIST.map((item, index) => `${index + 1}. [${item.id}] ${item.title} — ${item.description}`).join("\n");
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=checklist.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checklist.js","sourceRoot":"","sources":["../../src/ai/checklist.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAsC;IAClE,iBAAiB,EAAE,CAAC,kBAAkB,CAAC;IACvC,cAAc,EAAE;QACd,sBAAsB;QACtB,4BAA4B;QAC5B,sBAAsB;QACtB,sBAAsB;QACtB,qBAAqB;QACrB,yBAAyB;QACzB,6BAA6B;KAC9B;IACD,OAAO,EAAE;QACP,6BAA6B;QAC7B,2BAA2B;QAC3B,6BAA6B;QAC7B,qBAAqB;KACtB;IACD,eAAe,EAAE;QACf,+BAA+B;QAC/B,0BAA0B;QAC1B,2BAA2B;QAC3B,2BAA2B;KAC5B;IACD,gBAAgB,EAAE,CAAC,wBAAwB,EAAE,4BAA4B,EAAE,yBAAyB,CAAC;IACrG,eAAe,EAAE,CAAC,gBAAgB,CAAC;IACnC,mBAAmB,EAAE,CAAC,eAAe,EAAE,2BAA2B,EAAE,qBAAqB,CAAC;CAC3F,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAwB,IAAI,GAAG,CAAC;IAC9D,cAAc;IACd,eAAe;IACf,gBAAgB;IAChB,iBAAiB;IACjB,mBAAmB;CACpB,CAAC,CAAC;AAEH,wFAAwF;AACxF,SAAS,0BAA0B;IACjC,OAAO,MAAM,CAAC,OAAO,CAAC,iBAAiB,CAAC;SACrC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,KAAK,IAAI,KAAK,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;SAC1D,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA+BvC,0BAA0B,EAAE;;;;;;;;;uIASyG,CAAC;AAExI,MAAM,CAAC,MAAM,qBAAqB,GAAoB;IACpD;QACE,EAAE,EAAE,iBAAiB;QACrB,KAAK,EAAE,4BAA4B;QACnC,WAAW,EACT,23CAA23C;KAC93C;IACD;QACE,EAAE,EAAE,eAAe;QACnB,KAAK,EAAE,eAAe;QACtB,WAAW,EACT,mjDAAmjD;KACtjD;IACD;QACE,EAAE,EAAE,eAAe;QACnB,KAAK,EAAE,eAAe;QACtB,WAAW,EACT,k/BAAk/B;YACl/B,orCAAorC;YACprC,u7BAAu7B;YACv7B,gfAAgf;KACnf;IACD;QACE,EAAE,EAAE,SAAS;QACb,KAAK,EAAE,SAAS;QAChB,WAAW,EACT,qhCAAqhC;KACxhC;IACD;QACE,EAAE,EAAE,cAAc;QAClB,KAAK,EAAE,wBAAwB;QAC/B,WAAW,EACT,i+GAAi+G;KACp+G;IACD;QACE,EAAE,EAAE,gBAAgB;QACpB,KAAK,EAAE,gBAAgB;QACvB,WAAW,EACT,2jIAA2jI;KAC9jI;IACD;QACE,EAAE,EAAE,mBAAmB;QACvB,KAAK,EAAE,mBAAmB;QAC1B,WAAW,EACT,mqCAAmqC;KACtqC;CACF,CAAC;AAEF,MAAM,UAAU,wBAAwB;IACtC,OAAO,qBAAqB,CAAC,GAAG,CAC9B,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,KAAK,GAAG,CAAC,MAAM,IAAI,CAAC,EAAE,KAAK,IAAI,CAAC,KAAK,MAAM,IAAI,CAAC,WAAW,EAAE,CAClF,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verified command shapes for known CLIs. Anything not listed here needs a
|
|
3
|
+
* custom --cli-cmd template with {model} and {prompt} placeholders — we do
|
|
4
|
+
* not guess flags for CLIs we haven't verified.
|
|
5
|
+
*/
|
|
6
|
+
const PRESETS = {
|
|
7
|
+
claude: (model) => ({
|
|
8
|
+
command: "claude",
|
|
9
|
+
args: ["-p", "{prompt}", "--model", model],
|
|
10
|
+
promptVia: "arg",
|
|
11
|
+
}),
|
|
12
|
+
};
|
|
13
|
+
export function resolveCliPresetTemplate(preset, model) {
|
|
14
|
+
const factory = PRESETS[preset];
|
|
15
|
+
return factory ? factory(model) : null;
|
|
16
|
+
}
|
|
17
|
+
export function parseCustomCliCmd(template) {
|
|
18
|
+
const usesStdin = !template.includes("{prompt}");
|
|
19
|
+
const parts = template.split(/\s+/).filter(Boolean);
|
|
20
|
+
return {
|
|
21
|
+
command: parts[0] ?? "",
|
|
22
|
+
args: parts.slice(1),
|
|
23
|
+
promptVia: usesStdin ? "stdin" : "arg",
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export const KNOWN_PRESET_NAMES = Object.keys(PRESETS);
|
|
27
|
+
//# sourceMappingURL=cli-presets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-presets.js","sourceRoot":"","sources":["../../src/ai/cli-presets.ts"],"names":[],"mappings":"AAOA;;;;GAIG;AACH,MAAM,OAAO,GAA0D;IACrE,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAClB,OAAO,EAAE,QAAQ;QACjB,IAAI,EAAE,CAAC,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,KAAK,CAAC;QAC1C,SAAS,EAAE,KAAK;KACjB,CAAC;CACH,CAAC;AAEF,MAAM,UAAU,wBAAwB,CAAC,MAAc,EAAE,KAAa;IACpE,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAChC,OAAO,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,QAAgB;IAChD,MAAM,SAAS,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IACjD,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACpD,OAAO;QACL,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE;QACvB,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;QACpB,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK;KACvC,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC"}
|