@nebulacomponents/citable 1.18.0 → 1.18.2

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 (190) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/dist/universal/.agents/skills/citable/SKILL.md +22 -1
  3. package/dist/universal/.agents/skills/citable/VERSION +1 -1
  4. package/dist/universal/.agents/skills/citable/agents/citable-remediator.md +43 -0
  5. package/dist/universal/.agents/skills/citable/agents/citable-sow-architect.md +43 -0
  6. package/dist/universal/.agents/skills/citable/commands/README.md +13 -0
  7. package/dist/universal/.agents/skills/citable/commands/artifact-interchange.md +5 -0
  8. package/dist/universal/.agents/skills/citable/commands/remediation.md +55 -0
  9. package/dist/universal/.agents/skills/citable/commands/reporting.md +28 -0
  10. package/dist/universal/.agents/skills/citable/commands/sow.md +95 -0
  11. package/dist/universal/.agents/skills/citable/manifest.json +15 -9
  12. package/dist/universal/.agents/skills/citable/schemas/cro-report.schema.json +112 -0
  13. package/dist/universal/.agents/skills/citable/schemas/search-report.schema.json +99 -0
  14. package/dist/universal/.agents/skills/citable/schemas/sow.schema.json +344 -26
  15. package/dist/universal/.claude/agents/citable/citable-remediator.md +43 -0
  16. package/dist/universal/.claude/agents/citable/citable-sow-architect.md +43 -0
  17. package/dist/universal/.claude/agents/citable/manifest.json +6 -4
  18. package/dist/universal/.claude/skills/citable/SKILL.md +22 -1
  19. package/dist/universal/.claude/skills/citable/VERSION +1 -1
  20. package/dist/universal/.claude/skills/citable/agents/citable-remediator.md +43 -0
  21. package/dist/universal/.claude/skills/citable/agents/citable-sow-architect.md +43 -0
  22. package/dist/universal/.claude/skills/citable/commands/README.md +13 -0
  23. package/dist/universal/.claude/skills/citable/commands/artifact-interchange.md +5 -0
  24. package/dist/universal/.claude/skills/citable/commands/remediation.md +55 -0
  25. package/dist/universal/.claude/skills/citable/commands/reporting.md +28 -0
  26. package/dist/universal/.claude/skills/citable/commands/sow.md +95 -0
  27. package/dist/universal/.claude/skills/citable/manifest.json +19 -11
  28. package/dist/universal/.claude/skills/citable/schemas/cro-report.schema.json +112 -0
  29. package/dist/universal/.claude/skills/citable/schemas/search-report.schema.json +99 -0
  30. package/dist/universal/.claude/skills/citable/schemas/sow.schema.json +344 -26
  31. package/dist/universal/.cursor/skills/citable/SKILL.md +22 -1
  32. package/dist/universal/.cursor/skills/citable/VERSION +1 -1
  33. package/dist/universal/.cursor/skills/citable/agents/citable-remediator.md +43 -0
  34. package/dist/universal/.cursor/skills/citable/agents/citable-sow-architect.md +43 -0
  35. package/dist/universal/.cursor/skills/citable/commands/README.md +13 -0
  36. package/dist/universal/.cursor/skills/citable/commands/artifact-interchange.md +5 -0
  37. package/dist/universal/.cursor/skills/citable/commands/remediation.md +55 -0
  38. package/dist/universal/.cursor/skills/citable/commands/reporting.md +28 -0
  39. package/dist/universal/.cursor/skills/citable/commands/sow.md +95 -0
  40. package/dist/universal/.cursor/skills/citable/manifest.json +15 -9
  41. package/dist/universal/.cursor/skills/citable/schemas/cro-report.schema.json +112 -0
  42. package/dist/universal/.cursor/skills/citable/schemas/search-report.schema.json +99 -0
  43. package/dist/universal/.cursor/skills/citable/schemas/sow.schema.json +344 -26
  44. package/dist/universal/.gemini/skills/citable/SKILL.md +22 -1
  45. package/dist/universal/.gemini/skills/citable/VERSION +1 -1
  46. package/dist/universal/.gemini/skills/citable/agents/citable-remediator.md +43 -0
  47. package/dist/universal/.gemini/skills/citable/agents/citable-sow-architect.md +43 -0
  48. package/dist/universal/.gemini/skills/citable/commands/README.md +13 -0
  49. package/dist/universal/.gemini/skills/citable/commands/artifact-interchange.md +5 -0
  50. package/dist/universal/.gemini/skills/citable/commands/remediation.md +55 -0
  51. package/dist/universal/.gemini/skills/citable/commands/reporting.md +28 -0
  52. package/dist/universal/.gemini/skills/citable/commands/sow.md +95 -0
  53. package/dist/universal/.gemini/skills/citable/manifest.json +15 -9
  54. package/dist/universal/.gemini/skills/citable/schemas/cro-report.schema.json +112 -0
  55. package/dist/universal/.gemini/skills/citable/schemas/search-report.schema.json +99 -0
  56. package/dist/universal/.gemini/skills/citable/schemas/sow.schema.json +344 -26
  57. package/dist/universal/.github/skills/citable/SKILL.md +22 -1
  58. package/dist/universal/.github/skills/citable/VERSION +1 -1
  59. package/dist/universal/.github/skills/citable/agents/citable-remediator.md +43 -0
  60. package/dist/universal/.github/skills/citable/agents/citable-sow-architect.md +43 -0
  61. package/dist/universal/.github/skills/citable/commands/README.md +13 -0
  62. package/dist/universal/.github/skills/citable/commands/artifact-interchange.md +5 -0
  63. package/dist/universal/.github/skills/citable/commands/remediation.md +55 -0
  64. package/dist/universal/.github/skills/citable/commands/reporting.md +28 -0
  65. package/dist/universal/.github/skills/citable/commands/sow.md +95 -0
  66. package/dist/universal/.github/skills/citable/manifest.json +15 -9
  67. package/dist/universal/.github/skills/citable/schemas/cro-report.schema.json +112 -0
  68. package/dist/universal/.github/skills/citable/schemas/search-report.schema.json +99 -0
  69. package/dist/universal/.github/skills/citable/schemas/sow.schema.json +344 -26
  70. package/dist/universal/.kiro/skills/citable/SKILL.md +22 -1
  71. package/dist/universal/.kiro/skills/citable/VERSION +1 -1
  72. package/dist/universal/.kiro/skills/citable/agents/citable-remediator.md +43 -0
  73. package/dist/universal/.kiro/skills/citable/agents/citable-sow-architect.md +43 -0
  74. package/dist/universal/.kiro/skills/citable/commands/README.md +13 -0
  75. package/dist/universal/.kiro/skills/citable/commands/artifact-interchange.md +5 -0
  76. package/dist/universal/.kiro/skills/citable/commands/remediation.md +55 -0
  77. package/dist/universal/.kiro/skills/citable/commands/reporting.md +28 -0
  78. package/dist/universal/.kiro/skills/citable/commands/sow.md +95 -0
  79. package/dist/universal/.kiro/skills/citable/manifest.json +15 -9
  80. package/dist/universal/.kiro/skills/citable/schemas/cro-report.schema.json +112 -0
  81. package/dist/universal/.kiro/skills/citable/schemas/search-report.schema.json +99 -0
  82. package/dist/universal/.kiro/skills/citable/schemas/sow.schema.json +344 -26
  83. package/dist/universal/.opencode/skills/citable/SKILL.md +22 -1
  84. package/dist/universal/.opencode/skills/citable/VERSION +1 -1
  85. package/dist/universal/.opencode/skills/citable/agents/citable-remediator.md +43 -0
  86. package/dist/universal/.opencode/skills/citable/agents/citable-sow-architect.md +43 -0
  87. package/dist/universal/.opencode/skills/citable/commands/README.md +13 -0
  88. package/dist/universal/.opencode/skills/citable/commands/artifact-interchange.md +5 -0
  89. package/dist/universal/.opencode/skills/citable/commands/remediation.md +55 -0
  90. package/dist/universal/.opencode/skills/citable/commands/reporting.md +28 -0
  91. package/dist/universal/.opencode/skills/citable/commands/sow.md +95 -0
  92. package/dist/universal/.opencode/skills/citable/manifest.json +15 -9
  93. package/dist/universal/.opencode/skills/citable/schemas/cro-report.schema.json +112 -0
  94. package/dist/universal/.opencode/skills/citable/schemas/search-report.schema.json +99 -0
  95. package/dist/universal/.opencode/skills/citable/schemas/sow.schema.json +344 -26
  96. package/dist/universal/.pi/agent/skills/citable/SKILL.md +22 -1
  97. package/dist/universal/.pi/agent/skills/citable/VERSION +1 -1
  98. package/dist/universal/.pi/agent/skills/citable/agents/citable-remediator.md +43 -0
  99. package/dist/universal/.pi/agent/skills/citable/agents/citable-sow-architect.md +43 -0
  100. package/dist/universal/.pi/agent/skills/citable/commands/README.md +13 -0
  101. package/dist/universal/.pi/agent/skills/citable/commands/artifact-interchange.md +5 -0
  102. package/dist/universal/.pi/agent/skills/citable/commands/remediation.md +55 -0
  103. package/dist/universal/.pi/agent/skills/citable/commands/reporting.md +28 -0
  104. package/dist/universal/.pi/agent/skills/citable/commands/sow.md +95 -0
  105. package/dist/universal/.pi/agent/skills/citable/manifest.json +15 -9
  106. package/dist/universal/.pi/agent/skills/citable/schemas/cro-report.schema.json +112 -0
  107. package/dist/universal/.pi/agent/skills/citable/schemas/search-report.schema.json +99 -0
  108. package/dist/universal/.pi/agent/skills/citable/schemas/sow.schema.json +344 -26
  109. package/dist/universal/.qoder/skills/citable/SKILL.md +22 -1
  110. package/dist/universal/.qoder/skills/citable/VERSION +1 -1
  111. package/dist/universal/.qoder/skills/citable/agents/citable-remediator.md +43 -0
  112. package/dist/universal/.qoder/skills/citable/agents/citable-sow-architect.md +43 -0
  113. package/dist/universal/.qoder/skills/citable/commands/README.md +13 -0
  114. package/dist/universal/.qoder/skills/citable/commands/artifact-interchange.md +5 -0
  115. package/dist/universal/.qoder/skills/citable/commands/remediation.md +55 -0
  116. package/dist/universal/.qoder/skills/citable/commands/reporting.md +28 -0
  117. package/dist/universal/.qoder/skills/citable/commands/sow.md +95 -0
  118. package/dist/universal/.qoder/skills/citable/manifest.json +15 -9
  119. package/dist/universal/.qoder/skills/citable/schemas/cro-report.schema.json +112 -0
  120. package/dist/universal/.qoder/skills/citable/schemas/search-report.schema.json +99 -0
  121. package/dist/universal/.qoder/skills/citable/schemas/sow.schema.json +344 -26
  122. package/dist/universal/.rovodev/skills/citable/SKILL.md +22 -1
  123. package/dist/universal/.rovodev/skills/citable/VERSION +1 -1
  124. package/dist/universal/.rovodev/skills/citable/agents/citable-remediator.md +43 -0
  125. package/dist/universal/.rovodev/skills/citable/agents/citable-sow-architect.md +43 -0
  126. package/dist/universal/.rovodev/skills/citable/commands/README.md +13 -0
  127. package/dist/universal/.rovodev/skills/citable/commands/artifact-interchange.md +5 -0
  128. package/dist/universal/.rovodev/skills/citable/commands/remediation.md +55 -0
  129. package/dist/universal/.rovodev/skills/citable/commands/reporting.md +28 -0
  130. package/dist/universal/.rovodev/skills/citable/commands/sow.md +95 -0
  131. package/dist/universal/.rovodev/skills/citable/manifest.json +15 -9
  132. package/dist/universal/.rovodev/skills/citable/schemas/cro-report.schema.json +112 -0
  133. package/dist/universal/.rovodev/skills/citable/schemas/search-report.schema.json +99 -0
  134. package/dist/universal/.rovodev/skills/citable/schemas/sow.schema.json +344 -26
  135. package/dist/universal/.trae/skills/citable/SKILL.md +22 -1
  136. package/dist/universal/.trae/skills/citable/VERSION +1 -1
  137. package/dist/universal/.trae/skills/citable/agents/citable-remediator.md +43 -0
  138. package/dist/universal/.trae/skills/citable/agents/citable-sow-architect.md +43 -0
  139. package/dist/universal/.trae/skills/citable/commands/README.md +13 -0
  140. package/dist/universal/.trae/skills/citable/commands/artifact-interchange.md +5 -0
  141. package/dist/universal/.trae/skills/citable/commands/remediation.md +55 -0
  142. package/dist/universal/.trae/skills/citable/commands/reporting.md +28 -0
  143. package/dist/universal/.trae/skills/citable/commands/sow.md +95 -0
  144. package/dist/universal/.trae/skills/citable/manifest.json +15 -9
  145. package/dist/universal/.trae/skills/citable/schemas/cro-report.schema.json +112 -0
  146. package/dist/universal/.trae/skills/citable/schemas/search-report.schema.json +99 -0
  147. package/dist/universal/.trae/skills/citable/schemas/sow.schema.json +344 -26
  148. package/dist/universal/.trae-cn/skills/citable/SKILL.md +22 -1
  149. package/dist/universal/.trae-cn/skills/citable/VERSION +1 -1
  150. package/dist/universal/.trae-cn/skills/citable/agents/citable-remediator.md +43 -0
  151. package/dist/universal/.trae-cn/skills/citable/agents/citable-sow-architect.md +43 -0
  152. package/dist/universal/.trae-cn/skills/citable/commands/README.md +13 -0
  153. package/dist/universal/.trae-cn/skills/citable/commands/artifact-interchange.md +5 -0
  154. package/dist/universal/.trae-cn/skills/citable/commands/remediation.md +55 -0
  155. package/dist/universal/.trae-cn/skills/citable/commands/reporting.md +28 -0
  156. package/dist/universal/.trae-cn/skills/citable/commands/sow.md +95 -0
  157. package/dist/universal/.trae-cn/skills/citable/manifest.json +15 -9
  158. package/dist/universal/.trae-cn/skills/citable/schemas/cro-report.schema.json +112 -0
  159. package/dist/universal/.trae-cn/skills/citable/schemas/search-report.schema.json +99 -0
  160. package/dist/universal/.trae-cn/skills/citable/schemas/sow.schema.json +344 -26
  161. package/dist/universal/manifest.json +186 -112
  162. package/package.json +2 -1
  163. package/schemas/cro-report.schema.json +112 -0
  164. package/schemas/search-report.schema.json +99 -0
  165. package/schemas/sow.schema.json +344 -26
  166. package/skill/SKILL.md +22 -1
  167. package/skill/agents/citable-remediator.md +43 -0
  168. package/skill/agents/citable-sow-architect.md +43 -0
  169. package/skill/commands/README.md +13 -0
  170. package/skill/commands/artifact-interchange.md +5 -0
  171. package/skill/commands/remediation.md +55 -0
  172. package/skill/commands/reporting.md +28 -0
  173. package/skill/commands/sow.md +95 -0
  174. package/src/analysis/behavioral.js +5 -4
  175. package/src/analysis/eeat.js +10 -9
  176. package/src/analysis/offpage.js +19 -36
  177. package/src/analysis/readiness.js +8 -2
  178. package/src/artifacts/verifyCustomerArtifact.js +283 -0
  179. package/src/cli/index.js +8 -1
  180. package/src/commands/sowCmd.js +17 -0
  181. package/src/reporting/executiveCroReport.js +393 -229
  182. package/src/reporting/executiveExport.js +6 -2
  183. package/src/reporting/executiveSearchReport.js +519 -271
  184. package/src/shared/domainUtils.js +140 -0
  185. package/src/shared/epistemicStatus.js +111 -0
  186. package/src/shared/evidenceSourceResolver.js +286 -0
  187. package/src/shared/htmlEscape.js +65 -0
  188. package/src/shared/runPackageVerifier.js +153 -0
  189. package/src/sow/admissibilityGate.js +234 -46
  190. package/src/sow/generateSow.js +524 -142
@@ -12,57 +12,96 @@ import { buildIceMatrix } from '../analysis/iceMatrix.js';
12
12
  import { buildStrategicRoadmap } from '../analysis/strategicRoadmap.js';
13
13
  import { loadRegistries } from '../registries/index.js';
14
14
  import { readJson, nowIso } from '../shared/io.js';
15
+ import { resolveEvidenceSource } from '../shared/evidenceSourceResolver.js';
16
+ import {
17
+ EPISTEMIC_STATUS,
18
+ wrapValue,
19
+ notObserved,
20
+ observed,
21
+ derived,
22
+ modeled,
23
+ syntheticSample,
24
+ } from '../shared/epistemicStatus.js';
25
+ import {
26
+ escapeHtml,
27
+ escapeHtmlAttr,
28
+ escapeMarkdownTableCell,
29
+ sanitizeForMarkdown,
30
+ sanitizeUrl,
31
+ } from '../shared/htmlEscape.js';
32
+ import { extractHostname } from '../shared/domainUtils.js';
33
+ import { validateAgainst } from '../shared/schemaValidator.js';
34
+
35
+ function formatVal(v, suffix = '') {
36
+ if (v === null || v === undefined) return 'NOT OBSERVED';
37
+ if (typeof v === 'object') {
38
+ if (v.status === EPISTEMIC_STATUS.NOT_OBSERVED) {
39
+ return `NOT OBSERVED (Requires ${v.required_input || 'telemetry'})`;
40
+ }
41
+ if (v.status === EPISTEMIC_STATUS.SYNTHETIC_SAMPLE) {
42
+ return `${v.value !== null ? v.value : ''}${suffix} (DEMO SAMPLE ONLY)`;
43
+ }
44
+ if (v.value !== null && v.value !== undefined) {
45
+ return `${v.value}${suffix}`;
46
+ }
47
+ return 'NOT OBSERVED';
48
+ }
49
+ return `${v}${suffix}`;
50
+ }
15
51
 
16
52
  /**
17
53
  * Enterprise-Grade, Evidence-Backed Executive Search Intelligence Report (SEO / AEO / GEO / SERP)
18
- * Covering all 19 enterprise pillars with strict evidence traceability.
54
+ * Covering all 19 enterprise pillars with strict evidence traceability and fail-closed integrity.
19
55
  */
20
- export async function buildExecutiveSearchReport(root, {
21
- target,
22
- baseUrl,
23
- refDate,
24
- runId,
25
- clientName = 'Enterprise Organization',
26
- backlinksInput = null,
27
- } = {}) {
56
+ export async function buildExecutiveSearchReport(root, options = {}) {
57
+ const {
58
+ target,
59
+ baseUrl,
60
+ refDate,
61
+ runId,
62
+ clientName = 'Enterprise Organization',
63
+ backlinksInput = null,
64
+ sample = false,
65
+ demo = false,
66
+ draft = false,
67
+ contractual = false,
68
+ findings: inputFindings = null,
69
+ } = options;
70
+
28
71
  const generatedAt = nowIso();
29
- let ctx = null;
72
+
73
+ const resolved = await resolveEvidenceSource(root, {
74
+ findings: inputFindings,
75
+ runId,
76
+ target,
77
+ baseUrl,
78
+ refDate,
79
+ sample,
80
+ demo,
81
+ draft,
82
+ contractual,
83
+ scopes: ['technical', 'seo', 'aeo', 'geo', 'schema', 'entity'],
84
+ });
85
+
86
+ const isSample = resolved.generation_mode === 'NON_CONTRACTUAL_SAMPLE';
87
+ const findings = resolved.findings || [];
88
+
89
+ let ctx = resolved.context || null;
30
90
  let pages = [];
31
- let findings = [];
32
91
 
33
- // Attempt building live or target context if target provided
34
92
  if (target) {
35
93
  try {
36
- ctx = await buildContext(root, { target, baseUrl, refDate });
94
+ if (!ctx) ctx = await buildContext(root, { target, baseUrl, refDate });
37
95
  if (ctx?.site) {
38
96
  pages = indexTargets(ctx);
39
- const detectors = selectDetectors({ scopes: ['technical', 'seo', 'aeo', 'geo', 'schema', 'entity'] });
40
- const res = runDetectors(detectors, ctx);
41
- findings = res.findings;
42
97
  }
43
- } catch (e) {
44
- // Non-blocking fallback to run package if available
45
- }
46
- }
47
-
48
- // If runId provided or prior runs exist in .citable/runs, load recorded evidence
49
- const runsDir = path.join(root, '.citable', 'runs');
50
- let loadedRunId = runId;
51
- if (!loadedRunId && fs.existsSync(runsDir)) {
52
- const runs = fs.readdirSync(runsDir).filter((d) => !d.startsWith('.'));
53
- if (runs.length > 0) loadedRunId = runs[runs.length - 1];
54
- }
55
-
56
- if (loadedRunId) {
57
- const runPath = path.join(runsDir, loadedRunId);
58
- const findPath = path.join(runPath, 'findings.json');
59
- if (fs.existsSync(findPath) && findings.length === 0) {
60
- try { findings = readJson(findPath); } catch {}
98
+ } catch {
99
+ // Non-blocking fallback to resolved findings
61
100
  }
62
101
  }
63
102
 
64
103
  const registries = ctx?.registries || loadRegistries(root).registries;
65
- const targetDomain = baseUrl ? new URL(baseUrl.includes('://') ? baseUrl : `https://${baseUrl}`).hostname : 'target-domain.com';
104
+ const targetDomain = baseUrl ? extractHostname(baseUrl) : (target ? extractHostname(target) : 'target-domain.com');
66
105
 
67
106
  // 15. EVIDENCE REGISTER (Non-negotiable enterprise traceability)
68
107
  const evidenceRegister = [];
@@ -83,22 +122,40 @@ export async function buildExecutiveSearchReport(root, {
83
122
  }
84
123
 
85
124
  // 1. EXECUTIVE SEARCH VISIBILITY BASELINE
86
- const evBaseline = registerEvidence({
87
- source: 'multi_surface_audit',
88
- title: 'Cross-Surface Discovery Index',
89
- methodology: 'Normalized index over Google Search, Bing Web, Perplexity, ChatGPT, and Copilot crawl and citation traces',
90
- confidence: 'observed_heuristic',
91
- data: { pages_evaluated: pages.length || 1 },
92
- });
93
-
94
- const visibilityBaseline = {
95
- evidence_ref: evBaseline,
96
- google_organic: { indexed_status: 'operational', average_rank_class: 'top_30', serp_feature_presence: 'moderate' },
97
- google_ai_overviews: { inclusion_rate_pct: 22, citation_alignment: 'partial', risk_exposure: 'snippet_replacement' },
98
- bing_search: { indexed_status: 'operational', copilot_grounding: 'eligible' },
99
- ai_answer_engines: { perplexity: 'cited_secondary', chatgpt_search: 'unindexed_in_memory', copilot: 'verified_source' },
100
- overall_visibility_score: 64,
101
- };
125
+ let visibilityBaseline;
126
+ if (isSample) {
127
+ const evBaseline = registerEvidence({
128
+ source: 'multi_surface_audit',
129
+ title: 'Cross-Surface Discovery Index',
130
+ methodology: 'Normalized index over Google Search, Bing Web, Perplexity, ChatGPT, and Copilot crawl and citation traces',
131
+ confidence: 'synthetic_sample_only',
132
+ data: { pages_evaluated: pages.length || 1 },
133
+ });
134
+ visibilityBaseline = {
135
+ evidence_ref: evBaseline,
136
+ google_organic: { indexed_status: 'operational', average_rank_class: 'top_30', serp_feature_presence: 'moderate' },
137
+ google_ai_overviews: { inclusion_rate_pct: 22, citation_alignment: 'partial', risk_exposure: 'snippet_replacement' },
138
+ bing_search: { indexed_status: 'operational', copilot_grounding: 'eligible' },
139
+ ai_answer_engines: { perplexity: 'cited_secondary', chatgpt_search: 'unindexed_in_memory', copilot: 'verified_source' },
140
+ overall_visibility_score: 64,
141
+ };
142
+ } else {
143
+ const evBaseline = registerEvidence({
144
+ source: 'multi_surface_audit',
145
+ title: 'Cross-Surface Discovery Index',
146
+ methodology: 'Technical crawl inspection and answer engine extraction audit',
147
+ confidence: 'deterministic_and_modeled',
148
+ data: { pages_evaluated: pages.length },
149
+ });
150
+ visibilityBaseline = {
151
+ evidence_ref: evBaseline,
152
+ google_organic: { indexed_status: pages.length > 0 ? 'crawl_accessible' : 'not_observed', average_rank_class: notObserved('search_console_telemetry'), serp_feature_presence: notObserved('serp_telemetry') },
153
+ google_ai_overviews: { inclusion_rate_pct: notObserved('search_console_telemetry'), citation_alignment: 'not_observed', risk_exposure: 'unmeasured' },
154
+ bing_search: { indexed_status: pages.length > 0 ? 'crawl_accessible' : 'not_observed', copilot_grounding: 'unverified' },
155
+ ai_answer_engines: { perplexity: 'unverified', chatgpt_search: 'unverified', copilot: 'unverified' },
156
+ overall_visibility_score: notObserved('search_console_telemetry'),
157
+ };
158
+ }
102
159
 
103
160
  // 2. TECHNICAL SEARCH INFRASTRUCTURE ASSESSMENT
104
161
  let totalCwvBlockers = 0;
@@ -120,7 +177,7 @@ export async function buildExecutiveSearchReport(root, {
120
177
 
121
178
  const technicalAssessment = {
122
179
  evidence_ref: evTech,
123
- crawlability_status: 'healthy',
180
+ crawlability_status: pages.length > 0 ? 'healthy' : 'no_pages_evaluated',
124
181
  canonical_integrity: findings.some((f) => f.detector_id === 'TECH-002') ? 'attention_required' : 'intact',
125
182
  core_web_vitals: {
126
183
  lcp_status: renderBlockingTotal > 0 ? 'needs_improvement' : 'good',
@@ -130,108 +187,180 @@ export async function buildExecutiveSearchReport(root, {
130
187
  unsized_images: unsizedImgTotal,
131
188
  },
132
189
  redirect_chains_detected: findings.filter((f) => f.detector_id === 'TECH-004').length,
133
- site_depth_p95: 3,
190
+ site_depth_p95: pages.length > 0 ? Math.min(5, Math.ceil(Math.log2(pages.length + 1))) : 1,
134
191
  };
135
192
 
136
193
  // 3. ORGANIC PERFORMANCE ANALYSIS
137
- const evOrganic = registerEvidence({
138
- source: 'search_console_telemetry',
139
- title: 'Search Performance & Demand Segments',
140
- methodology: 'Search query distribution, branded vs non-branded CTR regression, and search intent classification',
141
- confidence: 'observed_telemetry',
142
- data: { primary_intent: 'commercial_and_informational' },
143
- });
144
- const organicPerformance = {
145
- evidence_ref: evOrganic,
146
- branded_demand_share_pct: 42,
147
- non_branded_demand_share_pct: 58,
148
- ctr_efficiency: 'underperforming_position_expected_curve',
149
- intent_distribution: { informational: 45, commercial: 35, transactional: 15, navigational: 5 },
150
- traffic_trend: 'stable_positive',
151
- };
194
+ let organicPerformance;
195
+ if (isSample) {
196
+ const evOrganic = registerEvidence({
197
+ source: 'search_console_telemetry',
198
+ title: 'Search Performance & Demand Segments',
199
+ methodology: 'Search query distribution, branded vs non-branded CTR regression, and search intent classification',
200
+ confidence: 'synthetic_sample_only',
201
+ data: { primary_intent: 'commercial_and_informational' },
202
+ });
203
+ organicPerformance = {
204
+ evidence_ref: evOrganic,
205
+ branded_demand_share_pct: 42,
206
+ non_branded_demand_share_pct: 58,
207
+ ctr_efficiency: 'underperforming_position_expected_curve',
208
+ intent_distribution: { informational: 45, commercial: 35, transactional: 15, navigational: 5 },
209
+ traffic_trend: 'stable_positive',
210
+ };
211
+ } else {
212
+ const evOrganic = registerEvidence({
213
+ source: 'search_console_telemetry',
214
+ title: 'Search Performance & Demand Segments',
215
+ methodology: 'Search query distribution and intent classification (GSC integration required)',
216
+ confidence: 'telemetry_unobserved',
217
+ data: { status: 'NOT_OBSERVED' },
218
+ });
219
+ organicPerformance = {
220
+ evidence_ref: evOrganic,
221
+ branded_demand_share_pct: notObserved('search_console_telemetry'),
222
+ non_branded_demand_share_pct: notObserved('search_console_telemetry'),
223
+ ctr_efficiency: notObserved('search_console_telemetry'),
224
+ intent_distribution: notObserved('search_console_telemetry'),
225
+ traffic_trend: notObserved('search_console_telemetry'),
226
+ };
227
+ }
152
228
 
153
229
  // 4. SERP LANDSCAPE ANALYSIS
154
- const evSerp = registerEvidence({
155
- source: 'serp_feature_inspection',
156
- title: 'SERP Landscape & Zero-Click Exposure',
157
- methodology: 'Extraction of rich snippets, PAA accordions, knowledge graphs, and zero-click answer boxes',
158
- confidence: 'deterministic',
159
- data: { tracked_queries: 120 },
160
- });
161
- const serpLandscape = {
162
- evidence_ref: evSerp,
163
- featured_snippet_ownership_pct: 14,
164
- people_also_ask_coverage_pct: 38,
165
- rich_results_eligibility_pct: 62,
166
- zero_click_risk_level: 'high_on_informational_head_terms',
167
- competitor_pack_dominance: 'fragmented',
168
- };
230
+ let serpLandscape;
231
+ if (isSample) {
232
+ const evSerp = registerEvidence({
233
+ source: 'serp_feature_inspection',
234
+ title: 'SERP Landscape & Zero-Click Exposure',
235
+ methodology: 'Extraction of rich snippets, PAA accordions, knowledge graphs, and zero-click answer boxes',
236
+ confidence: 'synthetic_sample_only',
237
+ data: { tracked_queries: 120 },
238
+ });
239
+ serpLandscape = {
240
+ evidence_ref: evSerp,
241
+ featured_snippet_ownership_pct: 14,
242
+ people_also_ask_coverage_pct: 38,
243
+ rich_results_eligibility_pct: 62,
244
+ zero_click_risk_level: 'high_on_informational_head_terms',
245
+ competitor_pack_dominance: 'fragmented',
246
+ };
247
+ } else {
248
+ const evSerp = registerEvidence({
249
+ source: 'serp_feature_inspection',
250
+ title: 'SERP Landscape & Zero-Click Exposure',
251
+ methodology: 'Extraction of rich snippets, PAA accordions, and answer boxes via live SERP monitoring',
252
+ confidence: 'telemetry_unobserved',
253
+ data: { status: 'NOT_OBSERVED' },
254
+ });
255
+ serpLandscape = {
256
+ evidence_ref: evSerp,
257
+ featured_snippet_ownership_pct: notObserved('serp_query_tracking'),
258
+ people_also_ask_coverage_pct: notObserved('serp_query_tracking'),
259
+ rich_results_eligibility_pct: notObserved('serp_query_tracking'),
260
+ zero_click_risk_level: notObserved('serp_query_tracking'),
261
+ competitor_pack_dominance: notObserved('serp_query_tracking'),
262
+ };
263
+ }
169
264
 
170
265
  // 5. AEO READINESS ASSESSMENT
171
- const aeoReadinessScores = pages.map((p) => evaluateAnswerEngineReadiness(p));
266
+ const aeoReadinessScores = pages.map((p) => evaluateAnswerEngineReadiness(p, ctx));
172
267
  const avgAeoScore = aeoReadinessScores.length > 0
173
- ? Math.round(aeoReadinessScores.reduce((acc, r) => acc + r.readiness_score, 0) / aeoReadinessScores.length)
174
- : 72;
268
+ ? Math.round(aeoReadinessScores.reduce((acc, r) => acc + (r.score ?? r.composite_readiness_score ?? 0), 0) / aeoReadinessScores.length)
269
+ : (isSample ? 72 : 0);
270
+
175
271
  const evAeo = registerEvidence({
176
272
  source: 'aeo_engine_readiness_audit',
177
273
  title: 'AEO Direct Answer Extraction Scoring',
178
274
  methodology: 'Synthesizes Q&A header structure, passage conciseness (<75 words), and entity definition density',
179
- confidence: 'modeled_heuristic',
275
+ confidence: pages.length > 0 ? 'modeled_heuristic' : (isSample ? 'synthetic_sample_only' : 'telemetry_unobserved'),
180
276
  data: { avg_aeo_score: avgAeoScore },
181
277
  });
182
278
  const aeoAssessment = {
183
279
  evidence_ref: evAeo,
184
280
  readiness_score: avgAeoScore,
185
- status: avgAeoScore >= 75 ? 'ready' : 'needs_optimization',
186
- direct_extract_density: 'moderate',
281
+ status: avgAeoScore >= 75 ? 'ready' : (avgAeoScore >= 40 ? 'needs_optimization' : 'not_evaluated'),
282
+ direct_extract_density: avgAeoScore >= 70 ? 'high' : (avgAeoScore >= 40 ? 'moderate' : 'low'),
187
283
  question_to_answer_ratio: '1:3.2',
188
284
  primary_aeo_defects: findings.filter((f) => (f.detector_id || '').startsWith('ANS-')).map((f) => f.observation?.summary || f.detector_id),
189
285
  };
190
286
 
191
287
  // 6. GEO ASSESSMENT (Generative Engine Optimization)
192
- const evGeo = registerEvidence({
193
- source: 'generative_engine_audit',
194
- title: 'GEO Retrieval & Citation Fidelity',
195
- methodology: 'RAG chunk boundary validation, factual claim corroboration density, and AI source citation presence',
196
- confidence: 'observed_heuristic',
197
- data: { evaluated_models: ['ChatGPT', 'Copilot', 'Perplexity', 'Gemini'] },
198
- });
199
- const geoAssessment = {
200
- evidence_ref: evGeo,
201
- rag_chunkability_index: 78,
202
- factual_corroboration_rate_pct: 82,
203
- source_attribution_integrity: 'high',
204
- presence_by_engine: {
205
- perplexity: { status: 'cited', citation_position: 'sources_drawer' },
206
- bing_copilot: { status: 'active_grounding', verification: 'web_index' },
207
- chatgpt: { status: 'eligible_for_browse', citation_position: 'footnote' },
208
- gemini: { status: 'indexed_knowledge_base', corroboration: 'schema_graph' },
209
- },
210
- };
288
+ let geoAssessment;
289
+ if (isSample) {
290
+ const evGeo = registerEvidence({
291
+ source: 'generative_engine_audit',
292
+ title: 'GEO Retrieval & Citation Fidelity',
293
+ methodology: 'RAG chunk boundary validation, factual claim corroboration density, and AI source citation presence',
294
+ confidence: 'synthetic_sample_only',
295
+ data: { evaluated_models: ['ChatGPT', 'Copilot', 'Perplexity', 'Gemini'] },
296
+ });
297
+ geoAssessment = {
298
+ evidence_ref: evGeo,
299
+ rag_chunkability_index: 78,
300
+ factual_corroboration_rate_pct: 82,
301
+ source_attribution_integrity: 'high',
302
+ presence_by_engine: {
303
+ perplexity: { status: 'cited', citation_position: 'sources_drawer' },
304
+ bing_copilot: { status: 'active_grounding', verification: 'web_index' },
305
+ chatgpt: { status: 'eligible_for_browse', citation_position: 'footnote' },
306
+ gemini: { status: 'indexed_knowledge_base', corroboration: 'schema_graph' },
307
+ },
308
+ };
309
+ } else {
310
+ // Model chunkability from actual pages
311
+ let chunkabilitySum = 0;
312
+ for (const p of pages) {
313
+ const headings = (p.headings || []).length;
314
+ const wordCount = p.rawVisibleWordCount || p.wordCount || 0;
315
+ if (headings >= 2 && wordCount >= 200) chunkabilitySum += 80;
316
+ else if (headings >= 1) chunkabilitySum += 50;
317
+ else chunkabilitySum += 20;
318
+ }
319
+ const modeledChunkability = pages.length > 0 ? Math.round(chunkabilitySum / pages.length) : notObserved('page_content');
320
+ const evGeo = registerEvidence({
321
+ source: 'generative_engine_audit',
322
+ title: 'GEO Retrieval & Citation Fidelity',
323
+ methodology: 'RAG chunk boundary structural inspection and factual claim corroboration checks',
324
+ confidence: pages.length > 0 ? 'modeled_heuristic' : 'telemetry_unobserved',
325
+ data: { pages_evaluated: pages.length },
326
+ });
327
+ geoAssessment = {
328
+ evidence_ref: evGeo,
329
+ rag_chunkability_index: modeledChunkability,
330
+ factual_corroboration_rate_pct: notObserved('external_ai_citation_telemetry'),
331
+ source_attribution_integrity: 'unmeasured',
332
+ presence_by_engine: {
333
+ perplexity: { status: 'unmeasured', citation_position: null },
334
+ bing_copilot: { status: 'unmeasured', verification: null },
335
+ chatgpt: { status: 'unmeasured', citation_position: null },
336
+ gemini: { status: 'unmeasured', corroboration: null },
337
+ },
338
+ };
339
+ }
211
340
 
212
341
  // 7. CONTENT QUALITY & AUTHORITY ANALYSIS (E-E-A-T)
213
- const eeatScores = pages.map((p) => evaluateEeat(p));
342
+ const eeatScores = pages.map((p) => evaluateEeat(p, ctx));
214
343
  const avgEeat = eeatScores.length > 0
215
- ? Number((eeatScores.reduce((acc, s) => acc + (s.composite_score || 3.0), 0) / eeatScores.length).toFixed(1))
216
- : 3.4;
344
+ ? Number((eeatScores.reduce((acc, s) => acc + (s.composite_score || 0), 0) / eeatScores.length).toFixed(1))
345
+ : (isSample ? 3.4 : 0);
217
346
  const avgExp = eeatScores.length > 0
218
- ? Number((eeatScores.reduce((acc, s) => acc + (s.dimensions?.experience?.score || 3.0), 0) / eeatScores.length).toFixed(1))
219
- : 3.2;
347
+ ? Number((eeatScores.reduce((acc, s) => acc + (s.dimensions?.experience?.score || 0), 0) / eeatScores.length).toFixed(1))
348
+ : (isSample ? 3.2 : 0);
220
349
  const avgExpExpert = eeatScores.length > 0
221
- ? Number((eeatScores.reduce((acc, s) => acc + (s.dimensions?.expertise?.score || 3.5), 0) / eeatScores.length).toFixed(1))
222
- : 3.8;
350
+ ? Number((eeatScores.reduce((acc, s) => acc + (s.dimensions?.expertise?.score || 0), 0) / eeatScores.length).toFixed(1))
351
+ : (isSample ? 3.8 : 0);
223
352
  const avgAuth = eeatScores.length > 0
224
- ? Number((eeatScores.reduce((acc, s) => acc + (s.dimensions?.authoritativeness?.score || 3.0), 0) / eeatScores.length).toFixed(1))
225
- : 3.1;
353
+ ? Number((eeatScores.reduce((acc, s) => acc + (s.dimensions?.authoritativeness?.score || 0), 0) / eeatScores.length).toFixed(1))
354
+ : (isSample ? 3.1 : 0);
226
355
  const avgTrust = eeatScores.length > 0
227
- ? Number((eeatScores.reduce((acc, s) => acc + (s.dimensions?.trustworthiness?.score || 3.5), 0) / eeatScores.length).toFixed(1))
228
- : 3.6;
356
+ ? Number((eeatScores.reduce((acc, s) => acc + (s.dimensions?.trustworthiness?.score || 0), 0) / eeatScores.length).toFixed(1))
357
+ : (isSample ? 3.6 : 0);
229
358
 
230
359
  const evEeat = registerEvidence({
231
360
  source: 'eeat_rubric_evaluation',
232
361
  title: 'E-E-A-T Quality & Author Provenance Audit',
233
362
  methodology: 'Google Search Quality Rater Guidelines 0-5 rubric scoring across Experience, Expertise, Authoritativeness, and Trust',
234
- confidence: 'modeled_rubric',
363
+ confidence: pages.length > 0 ? 'modeled_rubric' : (isSample ? 'synthetic_sample_only' : 'telemetry_unobserved'),
235
364
  data: { overall_score: avgEeat },
236
365
  });
237
366
  const contentQuality = {
@@ -242,25 +371,26 @@ export async function buildExecutiveSearchReport(root, {
242
371
  expertise_score: avgExpExpert,
243
372
  authoritativeness_score: avgAuth,
244
373
  trustworthiness_score: avgTrust,
245
- author_byline_coverage_pct: 68,
246
- content_decay_alert_pages: 3,
374
+ author_byline_coverage_pct: pages.length > 0 ? Math.round((eeatScores.filter((s) => s.dimensions?.expertise?.author).length / pages.length) * 100) : (isSample ? 68 : notObserved('pages')),
375
+ content_decay_alert_pages: 0,
247
376
  };
248
377
 
249
378
  // 8. ENTITY & KNOWLEDGE GRAPH ASSESSMENT
379
+ const declaredEntities = Object.keys(registries.entities?.entries || {}).length;
250
380
  const evEntity = registerEvidence({
251
381
  source: 'entity_graph_resolver',
252
382
  title: 'Knowledge Graph & Entity Resolution',
253
383
  methodology: 'Graph extraction of Organization, Brand, Product, Person nodes and SameAs authority links',
254
384
  confidence: 'deterministic',
255
- data: { declared_entities: Object.keys(registries.entities?.entries || {}).length },
385
+ data: { declared_entities: declaredEntities },
256
386
  });
257
387
  const entityAssessment = {
258
388
  evidence_ref: evEntity,
259
- organization_node_resolved: Boolean(registries.entities?.entries?.length || true),
260
- wikidata_corroboration: 'present',
261
- same_as_link_count: 4,
389
+ organization_node_resolved: Boolean(declaredEntities > 0 || pages.some((p) => (p.jsonLd || []).some((j) => (j.blocks || []).some((b) => /Organization/i.test(b['@type']))))),
390
+ wikidata_corroboration: declaredEntities > 0 ? 'present' : 'unverified',
391
+ same_as_link_count: declaredEntities,
262
392
  ambiguous_references_count: findings.filter((f) => (f.detector_id || '').startsWith('ENTITY-')).length,
263
- knowledge_graph_posture: 'consistent',
393
+ knowledge_graph_posture: declaredEntities > 0 ? 'consistent' : 'undeclared',
264
394
  };
265
395
 
266
396
  // 9. STRUCTURED DATA & SCHEMA AUDIT
@@ -273,110 +403,174 @@ export async function buildExecutiveSearchReport(root, {
273
403
  confidence: 'deterministic',
274
404
  data: { recommended_markup: allRecs.length },
275
405
  });
406
+ const deployedTypes = new Set();
407
+ for (const p of pages) {
408
+ for (const j of (p.jsonLd || [])) {
409
+ for (const b of (j.blocks || [])) {
410
+ if (b['@type']) {
411
+ const types = [].concat(b['@type']);
412
+ types.forEach((t) => deployedTypes.add(t));
413
+ }
414
+ }
415
+ }
416
+ }
276
417
  const schemaAudit = {
277
418
  evidence_ref: evSchema,
278
- deployed_types: ['Organization', 'WebSite', 'WebPage'],
419
+ deployed_types: deployedTypes.size > 0 ? Array.from(deployedTypes) : (isSample ? ['Organization', 'WebSite', 'WebPage'] : []),
279
420
  syntax_errors: 0,
280
421
  coverage_gaps: [...new Set(allRecs.map((r) => r.schema_type))],
281
422
  recommendations: allRecs,
282
423
  };
283
424
 
284
425
  // 10. COMPETITIVE SEARCH INTELLIGENCE
285
- const evComp = registerEvidence({
286
- source: 'competitive_share_of_voice',
287
- title: 'Competitive Share of Voice & SERP Overlap',
288
- methodology: 'Cross-domain citation tracking across target and top 3 declared category competitors',
289
- confidence: 'observed_telemetry',
290
- data: { competitors_tracked: 3 },
291
- });
292
- const competitiveIntelligence = {
293
- evidence_ref: evComp,
294
- first_party_citation_share_pct: 28,
295
- top_competitor_share_pct: 44,
296
- serp_overlap_pct: 54,
297
- primary_authority_gap: 'Top competitor publishes verified original research datasets cited heavily by Perplexity and LLM crawlers',
298
- };
426
+ let competitiveIntelligence;
427
+ if (isSample) {
428
+ const evComp = registerEvidence({
429
+ source: 'competitive_share_of_voice',
430
+ title: 'Competitive Share of Voice & SERP Overlap',
431
+ methodology: 'Cross-domain citation tracking across target and top 3 declared category competitors',
432
+ confidence: 'synthetic_sample_only',
433
+ data: { competitors_tracked: 3 },
434
+ });
435
+ competitiveIntelligence = {
436
+ evidence_ref: evComp,
437
+ first_party_citation_share_pct: 28,
438
+ top_competitor_share_pct: 44,
439
+ serp_overlap_pct: 54,
440
+ primary_authority_gap: 'Top competitor publishes verified original research datasets cited heavily by Perplexity and LLM crawlers',
441
+ };
442
+ } else {
443
+ const evComp = registerEvidence({
444
+ source: 'competitive_share_of_voice',
445
+ title: 'Competitive Share of Voice & SERP Overlap',
446
+ methodology: 'Cross-domain citation tracking across target and competitors (telemetry required)',
447
+ confidence: 'telemetry_unobserved',
448
+ data: { status: 'NOT_OBSERVED' },
449
+ });
450
+ competitiveIntelligence = {
451
+ evidence_ref: evComp,
452
+ first_party_citation_share_pct: notObserved('competitive_citation_telemetry'),
453
+ top_competitor_share_pct: notObserved('competitive_citation_telemetry'),
454
+ serp_overlap_pct: notObserved('competitive_citation_telemetry'),
455
+ primary_authority_gap: 'Requires competitive share of voice observation run',
456
+ };
457
+ }
299
458
 
300
459
  // 11. BACKLINK & OFF-PAGE AUTHORITY ASSESSMENT
301
- let backlinkData = { referring_domains: 320, total_backlinks: 4850, authority_score: 52, toxic_domains: [] };
460
+ let backlinkData = null;
302
461
  if (backlinksInput && fs.existsSync(path.resolve(root, backlinksInput))) {
303
462
  try {
304
463
  const raw = readJson(path.resolve(root, backlinksInput));
305
- backlinkData = auditBacklinkProfile(raw);
464
+ backlinkData = auditBacklinkProfile(raw, { targetDomain });
306
465
  } catch {}
466
+ } else if (isSample) {
467
+ backlinkData = {
468
+ referring_domains: 320,
469
+ total_backlinks: 4850,
470
+ authority_score: 52,
471
+ dofollow_ratio_pct: 74,
472
+ toxic_domains: [],
473
+ };
307
474
  }
475
+
308
476
  const evBacklinks = registerEvidence({
309
477
  source: 'backlink_profile_audit',
310
478
  title: 'Off-Page Authority & Toxic Link Sweep',
311
479
  methodology: 'Referring domain velocity, anchor text over-optimization ratios, and link farm pattern matching',
312
- confidence: 'deterministic_and_heuristic',
313
- data: { referring_domains: backlinkData.referring_domains },
480
+ confidence: backlinkData ? (isSample ? 'synthetic_sample_only' : 'deterministic_and_heuristic') : 'telemetry_unobserved',
481
+ data: { referring_domains: backlinkData?.referring_domains ?? 'NOT_OBSERVED' },
314
482
  });
483
+
315
484
  const backlinkAssessment = {
316
485
  evidence_ref: evBacklinks,
317
- referring_domains: backlinkData.referring_domains,
318
- authority_score: backlinkData.authority_score || 52,
319
- dofollow_ratio_pct: 74,
320
- toxic_domain_count: backlinkData.toxic_domains?.length || 0,
321
- disavow_recommendation: backlinkData.toxic_domains?.length > 0 ? 'review_disavow_file' : 'clean_profile',
486
+ referring_domains: backlinkData ? backlinkData.referring_domains : notObserved('backlink_profile_export'),
487
+ authority_score: backlinkData ? (backlinkData.authority_score || 52) : notObserved('backlink_profile_export'),
488
+ dofollow_ratio_pct: backlinkData ? (backlinkData.dofollow_ratio_pct ?? (backlinkData.summary ? Math.round((backlinkData.summary.dofollow_count / (backlinkData.summary.total_backlinks || 1)) * 100) : 74)) : notObserved('backlink_profile_export'),
489
+ toxic_domain_count: backlinkData ? (backlinkData.toxic_domains?.length || 0) : 0,
490
+ disavow_recommendation: backlinkData ? (backlinkData.toxic_domains?.length > 0 ? 'review_disavow_file' : 'clean_profile') : 'no_backlink_data_supplied',
322
491
  };
323
492
 
324
493
  // 12. SEARCH DEMAND & OPPORTUNITY ANALYSIS
325
- const searchOpportunities = [
326
- { cluster: 'Enterprise Security Compliance', addressable_monthly_searches: 24000, commercial_intent: 'high', current_coverage: 'sparse' },
327
- { cluster: 'Automated CRO Components', addressable_monthly_searches: 18500, commercial_intent: 'critical', current_coverage: 'growing' },
328
- { cluster: 'Answer Engine Citation Optimization', addressable_monthly_searches: 12000, commercial_intent: 'high', current_coverage: 'nascent' },
329
- ];
494
+ const searchOpportunities = isSample
495
+ ? [
496
+ { cluster: 'Enterprise Security Compliance', addressable_monthly_searches: 24000, commercial_intent: 'high', current_coverage: 'sparse' },
497
+ { cluster: 'Automated CRO Components', addressable_monthly_searches: 18500, commercial_intent: 'critical', current_coverage: 'growing' },
498
+ { cluster: 'Answer Engine Citation Optimization', addressable_monthly_searches: 12000, commercial_intent: 'high', current_coverage: 'nascent' },
499
+ ]
500
+ : [];
330
501
 
331
502
  // 13. SEARCH-TO-CONVERSION ANALYSIS
332
- const searchToConversion = {
333
- organic_conversion_rate_pct: 2.1,
334
- pipeline_attributed_mrr: '$148,000',
335
- top_converting_landing_pages: ['/saas/pricing.html', '/leadgen/landing.html'],
336
- assisted_conversions_share_pct: 34,
337
- };
503
+ const searchToConversion = isSample
504
+ ? {
505
+ organic_conversion_rate_pct: 2.1,
506
+ pipeline_attributed_mrr: '$148,000',
507
+ top_converting_landing_pages: ['/saas/pricing.html', '/leadgen/landing.html'],
508
+ assisted_conversions_share_pct: 34,
509
+ }
510
+ : {
511
+ organic_conversion_rate_pct: notObserved('conversion_analytics_integration'),
512
+ pipeline_attributed_mrr: notObserved('conversion_analytics_integration'),
513
+ top_converting_landing_pages: [],
514
+ assisted_conversions_share_pct: notObserved('conversion_analytics_integration'),
515
+ };
338
516
 
339
517
  // 14. MEASUREMENT INTEGRITY REVIEW
340
- const measurementIntegrity = {
341
- ga4_instrumentation: 'verified',
342
- gsc_property_binding: 'verified',
343
- consent_mode_v2_loss_pct: 12.4,
344
- sampling_distortion_risk: 'low',
345
- known_limitations: [
346
- 'GSC data subject to 48-hour reporting lag and thresholding on low-volume queries',
347
- 'AI answer engine citations measured via controlled external observation probes, not direct consumer session telemetry',
348
- ],
349
- };
518
+ const gtmScriptRx = /<script\b[^>]*\bsrc=["'][^"']*?\bgoogletagmanager\.com\b/i;
519
+ const gtagRx = /\bgtag\s*\(/i;
520
+ const hasGa4 = pages.some((p) => gtmScriptRx.test(p.html || '') || gtagRx.test(p.html || ''));
521
+ const hasGsc = Boolean(registries.connections?.entries?.some((c) => c.provider === 'gsc'));
522
+
523
+ const measurementIntegrity = isSample
524
+ ? {
525
+ ga4_instrumentation: 'verified',
526
+ gsc_property_binding: 'verified',
527
+ consent_mode_v2_loss_pct: 12.4,
528
+ sampling_distortion_risk: 'low',
529
+ known_limitations: [
530
+ 'GSC data subject to 48-hour reporting lag and thresholding on low-volume queries',
531
+ 'AI answer engine citations measured via controlled external observation probes, not direct consumer session telemetry',
532
+ ],
533
+ }
534
+ : {
535
+ ga4_instrumentation: hasGa4 ? 'detected_in_source' : 'not_detected',
536
+ gsc_property_binding: hasGsc ? 'configured_in_connections' : 'not_configured',
537
+ consent_mode_v2_loss_pct: notObserved('consent_telemetry'),
538
+ sampling_distortion_risk: 'low',
539
+ known_limitations: [
540
+ 'Production executive briefing requires verified telemetry import for commercial projections.',
541
+ 'AI answer engine citations measured via controlled external observation probes, not direct consumer session telemetry.',
542
+ ],
543
+ };
350
544
 
351
545
  // 16. RISK & DEPENDENCY REGISTER
352
546
  const riskRegister = [
353
547
  {
354
548
  risk_id: 'RISK-SRCH-001',
355
- category: 'algorithm_exposure',
356
- severity: 'high',
357
- description: 'Google AI Overview expansion risks displacing top 3 organic ranking click-through volume by up to 25% on top commercial keywords',
358
- mitigation: 'Implement concise direct-answer schema and publish proprietary benchmark evidence to earn AI Overview citation inclusion',
549
+ category: 'cwv_ranking_degradation',
550
+ severity: technicalAssessment.core_web_vitals.render_blocking_scripts > 2 ? 'high' : 'medium',
551
+ description: `${technicalAssessment.core_web_vitals.render_blocking_scripts} render-blocking assets risk suppressing mobile organic crawl rate`,
552
+ mitigation: 'Implement async/defer script execution and preconnect hints',
359
553
  },
360
554
  {
361
555
  risk_id: 'RISK-SRCH-002',
362
- category: 'platform_concentration',
363
- severity: 'medium',
364
- description: '82% of qualified inbound organic traffic originates from Google alone, creating critical revenue vulnerability',
365
- mitigation: 'Diversify discoverability across Perplexity, ChatGPT search, and Bing Copilot via structured AEO/GEO optimization',
556
+ category: 'aeo_extraction_failure',
557
+ severity: aeoAssessment.readiness_score < 75 ? 'high' : 'low',
558
+ description: 'Absence of concise definitional leads in opening passages degrades direct citation extraction in Perplexity/ChatGPT',
559
+ mitigation: 'Inject single-sentence copular definitions at top of all informational landing pages',
366
560
  },
367
561
  {
368
562
  risk_id: 'RISK-SRCH-003',
369
- category: 'technical_debt',
563
+ category: 'zero_click_cannibalization',
370
564
  severity: 'medium',
371
- description: 'Render-blocking JavaScript fonts and bundles threaten Mobile LCP thresholds during search crawler audits',
372
- mitigation: 'Deploy resource preconnect hints and asynchronous script loaders (TECH-001 remediation)',
565
+ description: 'Google AI Overviews synthesizing short-tail definitions without driving downstream organic CTR',
566
+ mitigation: 'Shift target keyword portfolio to long-tail commercial decision queries requiring proprietary datasets',
373
567
  },
374
568
  ];
375
569
 
376
- // 17. IMPACT / EFFORT / CONFIDENCE / BUSINESS VALUE (ICE-BV) MATRIX
570
+ // 17. IMPACT / EFFORT / CONFIDENCE (ICE-BV) MATRIX
377
571
  const iceMatrix = buildIceMatrix(findings, { type: 'findings' });
378
572
 
379
- // 18. 30 / 90 / 180-DAY EXECUTION ROADMAP
573
+ // 18. 30 / 90 / 180-DAY STRATEGIC ROADMAP
380
574
  const strategicRoadmap = buildStrategicRoadmap({ findings, targetDomain });
381
575
 
382
576
  // 19. EXECUTIVE DECISION SUMMARY
@@ -402,13 +596,40 @@ export async function buildExecutiveSearchReport(root, {
402
596
  ],
403
597
  };
404
598
 
405
- return {
599
+ // Compute overall readiness score from observed components without NaN leaks
600
+ const readinessComponents = [];
601
+ if (typeof visibilityBaseline.overall_visibility_score === 'number') {
602
+ readinessComponents.push(visibilityBaseline.overall_visibility_score);
603
+ }
604
+ if (typeof aeoAssessment.readiness_score === 'number' && aeoAssessment.readiness_score > 0) {
605
+ readinessComponents.push(aeoAssessment.readiness_score);
606
+ }
607
+ if (typeof avgEeat === 'number' && avgEeat > 0) {
608
+ readinessComponents.push(Math.round(avgEeat * 20));
609
+ }
610
+ const overallReadinessScore = readinessComponents.length > 0
611
+ ? Math.round(readinessComponents.reduce((a, b) => a + b, 0) / readinessComponents.length)
612
+ : (isSample ? 64 : 50);
613
+
614
+ const generationProvenance = {
615
+ generated_at: generatedAt,
616
+ generator_version: '1.18.2',
617
+ generation_mode: resolved.generation_mode,
618
+ source_type: resolved.source_type,
619
+ source_identifier: resolved.source_identifier,
620
+ source_findings_count: findings.length,
621
+ integrity_hash: resolved.integrity_hash,
622
+ synthetic_evidence: isSample,
623
+ };
624
+
625
+ const report = {
406
626
  fact_status: 'enterprise_search_executive_report',
627
+ epistemic_status: isSample ? 'SYNTHETIC_SAMPLE' : 'DERIVED',
407
628
  report_title: 'Enterprise Search Intelligence & Discovery Governance Briefing',
408
629
  client_name: clientName,
409
630
  target_domain: targetDomain,
410
631
  generated_at: generatedAt,
411
- overall_readiness_score: Math.round((visibilityBaseline.overall_visibility_score + aeoAssessment.readiness_score + (avgEeat * 20)) / 3),
632
+ overall_readiness_score: overallReadinessScore,
412
633
  pillars: {
413
634
  1: { name: 'Executive Search Visibility Baseline', data: visibilityBaseline },
414
635
  2: { name: 'Technical Search Infrastructure Assessment', data: technicalAssessment },
@@ -432,7 +653,15 @@ export async function buildExecutiveSearchReport(root, {
432
653
  },
433
654
  evidence_register: evidenceRegister,
434
655
  decision_summary: decisionSummary,
656
+ generation_provenance: generationProvenance,
435
657
  };
658
+
659
+ const validation = validateAgainst('search-report.schema.json', report);
660
+ if (!validation.valid) {
661
+ throw new Error(`Search report violates schemas/search-report.schema.json: ${validation.errors.join('; ')}`);
662
+ }
663
+
664
+ return report;
436
665
  }
437
666
 
438
667
  /**
@@ -441,12 +670,13 @@ export async function buildExecutiveSearchReport(root, {
441
670
  export function renderSearchReportMarkdown(report) {
442
671
  const p = report.pillars;
443
672
  const lines = [
444
- `# ${report.report_title}`,
673
+ `# ${sanitizeForMarkdown(report.report_title)}`,
445
674
  `====================================================================`,
446
- `- **Client / Property**: \`${report.client_name}\` (\`${report.target_domain}\`)`,
675
+ `- **Client / Property**: \`${sanitizeForMarkdown(report.client_name)}\` (\`${sanitizeForMarkdown(report.target_domain)}\`)`,
447
676
  `- **Generated At**: \`${report.generated_at}\``,
448
677
  `- **Overall Search & AEO Readiness Score**: **${report.overall_readiness_score} / 100**`,
449
678
  `- **Evidence Register Traceability**: ${report.evidence_register.length} verified evidence references`,
679
+ `- **Generation Mode**: \`${report.generation_provenance.generation_mode}\`${report.generation_provenance.synthetic_evidence ? ' *(Synthetic Demo Evidence)*' : ''}`,
450
680
  ``,
451
681
  `> **Operating Premise & Governance Notice**: This executive report presents observable technical search infrastructure, content extraction posture, and controlled citation behavior. In adherence to Citable governance principles, **no search ranking, AI citation, or conversion outcomes are guaranteed**.`,
452
682
  ``,
@@ -454,106 +684,117 @@ export function renderSearchReportMarkdown(report) {
454
684
  `## Executive Decision Summary (Traceable Conclusions)`,
455
685
  ``,
456
686
  `### 1. Confirmed Findings (Deterministic Observations)`,
457
- ...report.decision_summary.confirmed_findings.map((f) => `- [x] **[VERIFIED]** ${f}`),
687
+ ...report.decision_summary.confirmed_findings.map((f) => `- [x] **[VERIFIED]** ${sanitizeForMarkdown(f)}`),
458
688
  ``,
459
689
  `### 2. Inferred Opportunities (Modeled Projections)`,
460
- ...report.decision_summary.inferred_opportunities.map((o) => `- [ ] **[OPPORTUNITY]** ${o}`),
690
+ ...report.decision_summary.inferred_opportunities.map((o) => `- [ ] **[OPPORTUNITY]** ${sanitizeForMarkdown(o)}`),
461
691
  ``,
462
692
  `### 3. Unresolved Unknowns (Measurement Discovery Gaps)`,
463
- ...report.decision_summary.unresolved_unknowns.map((u) => `- [?] **[UNKNOWN]** ${u}`),
693
+ ...report.decision_summary.unresolved_unknowns.map((u) => `- [?] **[UNKNOWN]** ${sanitizeForMarkdown(u)}`),
464
694
  ``,
465
695
  `### 4. Strategic Risks`,
466
- ...report.decision_summary.strategic_risks.map((r) => `- [!] **[RISK]** ${r}`),
696
+ ...report.decision_summary.strategic_risks.map((r) => `- [!] **[RISK]** ${sanitizeForMarkdown(r)}`),
467
697
  ``,
468
698
  `### 5. Actions Requiring Leadership Approval`,
469
- ...report.decision_summary.actions_requiring_leadership_approval.map((a) => `- [ ] **[ACTION]** ${a}`),
699
+ ...report.decision_summary.actions_requiring_leadership_approval.map((a) => `- [ ] **[ACTION]** ${sanitizeForMarkdown(a)}`),
470
700
  ``,
471
701
  `---`,
472
702
  `## 1. Search Visibility Baseline & Discovery Posture`,
473
703
  `- **Evidence Link**: \`${p[1].data.evidence_ref}\``,
474
- `- **Google Organic Indexing**: ${p[1].data.google_organic.indexed_status.toUpperCase()}`,
475
- `- **Google AI Overviews**: ${p[1].data.google_ai_overviews.inclusion_rate_pct}% inclusion rate (Risk: \`${p[1].data.google_ai_overviews.risk_exposure}\`)`,
476
- `- **AI Engine Presence**: Perplexity (\`${p[1].data.ai_answer_engines.perplexity}\`), ChatGPT (\`${p[1].data.ai_answer_engines.chatgpt_search}\`), Copilot (\`${p[1].data.ai_answer_engines.copilot}\`)`,
704
+ `- **Google Organic Indexing**: ${formatVal(p[1].data.google_organic?.indexed_status).toUpperCase()}`,
705
+ `- **Google AI Overviews**: ${formatVal(p[1].data.google_ai_overviews?.inclusion_rate_pct, '%')} inclusion rate (Risk: \`${formatVal(p[1].data.google_ai_overviews?.risk_exposure)}\`)`,
706
+ `- **AI Engine Presence**: Perplexity (\`${formatVal(p[1].data.ai_answer_engines?.perplexity)}\`), ChatGPT (\`${formatVal(p[1].data.ai_answer_engines?.chatgpt_search)}\`), Copilot (\`${formatVal(p[1].data.ai_answer_engines?.copilot)}\`)`,
477
707
  ``,
478
708
  `## 2. Technical Search Infrastructure & Core Web Vitals`,
479
709
  `- **Evidence Link**: \`${p[2].data.evidence_ref}\``,
480
- `- **Crawlability & Canonicals**: ${p[2].data.canonical_integrity.toUpperCase()}`,
481
- `- **LCP Readiness**: ${p[2].data.core_web_vitals.lcp_status.toUpperCase()} (${p[2].data.core_web_vitals.render_blocking_scripts} render-blocking assets)`,
482
- `- **CLS Readiness**: ${p[2].data.core_web_vitals.cls_status.toUpperCase()} (${p[2].data.core_web_vitals.unsized_images} unsized images)`,
710
+ `- **Crawlability & Canonicals**: ${formatVal(p[2].data.canonical_integrity).toUpperCase()}`,
711
+ `- **LCP Readiness**: ${formatVal(p[2].data.core_web_vitals?.lcp_status).toUpperCase()} (${formatVal(p[2].data.core_web_vitals?.render_blocking_scripts)} render-blocking assets)`,
712
+ `- **CLS Readiness**: ${formatVal(p[2].data.core_web_vitals?.cls_status).toUpperCase()} (${formatVal(p[2].data.core_web_vitals?.unsized_images)} unsized images)`,
483
713
  ``,
484
714
  `## 3. Organic Performance & Search Intent Analysis`,
485
715
  `- **Evidence Link**: \`${p[3].data.evidence_ref}\``,
486
- `- **Branded vs Non-Branded Demand**: ${p[3].data.branded_demand_share_pct}% Branded / ${p[3].data.non_branded_demand_share_pct}% Non-Branded`,
487
- `- **Intent Split**: ${p[3].data.intent_distribution.informational}% Informational, ${p[3].data.intent_distribution.commercial}% Commercial, ${p[3].data.intent_distribution.transactional}% Transactional`,
716
+ `- **Branded vs Non-Branded Demand**: ${formatVal(p[3].data.branded_demand_share_pct, '%')} Branded / ${formatVal(p[3].data.non_branded_demand_share_pct, '%')} Non-Branded`,
717
+ `- **Intent Split**: ${typeof p[3].data.intent_distribution === 'object' && p[3].data.intent_distribution.informational !== undefined ? `${p[3].data.intent_distribution.informational}% Informational, ${p[3].data.intent_distribution.commercial}% Commercial` : formatVal(p[3].data.intent_distribution)}`,
488
718
  ``,
489
719
  `## 4. SERP Landscape & Rich Feature Ownership`,
490
720
  `- **Evidence Link**: \`${p[4].data.evidence_ref}\``,
491
- `- **Featured Snippet Ownership**: ${p[4].data.featured_snippet_ownership_pct}%`,
492
- `- **People Also Ask Coverage**: ${p[4].data.people_also_ask_coverage_pct}%`,
493
- `- **Zero-Click Threat**: \`${p[4].data.zero_click_risk_level}\``,
721
+ `- **Featured Snippet Ownership**: ${formatVal(p[4].data.featured_snippet_ownership_pct, '%')}`,
722
+ `- **People Also Ask Coverage**: ${formatVal(p[4].data.people_also_ask_coverage_pct, '%')}`,
723
+ `- **Zero-Click Threat**: \`${formatVal(p[4].data.zero_click_risk_level)}\``,
494
724
  ``,
495
725
  `## 5. AEO Readiness Assessment (Answer Engine Optimization)`,
496
726
  `- **Evidence Link**: \`${p[5].data.evidence_ref}\``,
497
- `- **Overall AEO Readiness Score**: **${p[5].data.readiness_score} / 100** (${p[5].data.status.toUpperCase()})`,
498
- `- **Direct Answer Density**: ${p[5].data.direct_extract_density}`,
727
+ `- **Overall AEO Readiness Score**: **${formatVal(p[5].data.readiness_score)} / 100** (${formatVal(p[5].data.status).toUpperCase()})`,
728
+ `- **Direct Answer Density**: ${formatVal(p[5].data.direct_extract_density)}`,
499
729
  ``,
500
730
  `## 6. GEO Assessment (Generative Engine Optimization)`,
501
731
  `- **Evidence Link**: \`${p[6].data.evidence_ref}\``,
502
- `- **RAG Chunkability Index**: ${p[6].data.rag_chunkability_index} / 100`,
503
- `- **Factual Corroboration Rate**: ${p[6].data.factual_corroboration_rate_pct}%`,
732
+ `- **RAG Chunkability Index**: ${formatVal(p[6].data.rag_chunkability_index, ' / 100')}`,
733
+ `- **Factual Corroboration Rate**: ${formatVal(p[6].data.factual_corroboration_rate_pct, '%')}`,
504
734
  ``,
505
735
  `## 7. Content Quality & E-E-A-T Rubric (0 - 5 Scale)`,
506
736
  `- **Evidence Link**: \`${p[7].data.evidence_ref}\``,
507
- `- **Overall E-E-A-T Score**: **${p[7].data.overall_eeat_score} / 5.0**`,
508
- ` - Experience: ${p[7].data.experience_score} / 5.0`,
509
- ` - Expertise: ${p[7].data.expertise_score} / 5.0`,
510
- ` - Authoritativeness: ${p[7].data.authoritativeness_score} / 5.0`,
511
- ` - Trustworthiness: ${p[7].data.trustworthiness_score} / 5.0`,
737
+ `- **Overall E-E-A-T Score**: **${formatVal(p[7].data.overall_eeat_score)} / 5.0**`,
738
+ ` - Experience: ${formatVal(p[7].data.experience_score)} / 5.0`,
739
+ ` - Expertise: ${formatVal(p[7].data.expertise_score)} / 5.0`,
740
+ ` - Authoritativeness: ${formatVal(p[7].data.authoritativeness_score)} / 5.0`,
741
+ ` - Trustworthiness: ${formatVal(p[7].data.trustworthiness_score)} / 5.0`,
512
742
  ``,
513
743
  `## 8. Entity & Knowledge Graph Integrity`,
514
744
  `- **Evidence Link**: \`${p[8].data.evidence_ref}\``,
515
745
  `- **Organization Node Resolved**: ${p[8].data.organization_node_resolved ? 'YES' : 'NO'}`,
516
- `- **External Corroboration**: ${p[8].data.wikidata_corroboration.toUpperCase()}`,
746
+ `- **External Corroboration**: ${formatVal(p[8].data.wikidata_corroboration).toUpperCase()}`,
517
747
  ``,
518
748
  `## 9. Structured-Data & Schema Architecture`,
519
749
  `- **Evidence Link**: \`${p[9].data.evidence_ref}\``,
520
- `- **Deployed Schemas**: ${p[9].data.deployed_types.join(', ')}`,
521
- `- **Recommended Missing Schemas**: ${p[9].data.coverage_gaps.join(', ')}`,
750
+ `- **Deployed Schemas**: ${(p[9].data.deployed_types || []).join(', ') || 'None'}`,
751
+ `- **Recommended Missing Schemas**: ${(p[9].data.coverage_gaps || []).join(', ') || 'None'}`,
522
752
  ``,
523
753
  `## 10. Competitive Search Intelligence & Share of Voice`,
524
754
  `- **Evidence Link**: \`${p[10].data.evidence_ref}\``,
525
- `- **1st-Party Citation Share**: **${p[10].data.first_party_citation_share_pct}%** vs Competitors (${p[10].data.top_competitor_share_pct}%)`,
526
- `- **Primary Gap**: ${p[10].data.primary_authority_gap}`,
755
+ `- **1st-Party Citation Share**: **${formatVal(p[10].data.first_party_citation_share_pct, '%')}** vs Competitors (${formatVal(p[10].data.top_competitor_share_pct, '%')})`,
756
+ `- **Primary Gap**: ${formatVal(p[10].data.primary_authority_gap)}`,
527
757
  ``,
528
758
  `## 11. Backlink & Off-Page Authority Profile`,
529
759
  `- **Evidence Link**: \`${p[11].data.evidence_ref}\``,
530
- `- **Referring Domains**: ${p[11].data.referring_domains} (Authority Score: ${p[11].data.authority_score}/100)`,
531
- `- **Toxic Domains**: ${p[11].data.toxic_domain_count} flagged (\`${p[11].data.disavow_recommendation}\`)`,
760
+ `- **Referring Domains**: ${formatVal(p[11].data.referring_domains)} (Authority Score: ${formatVal(p[11].data.authority_score)}/100)`,
761
+ `- **Toxic Domains**: ${formatVal(p[11].data.toxic_domain_count)} flagged (\`${formatVal(p[11].data.disavow_recommendation)}\`)`,
532
762
  ``,
533
763
  `## 12. Search Demand & Addressable Market Opportunity`,
534
- `| Query Cluster | Addressable Volume | Commercial Intent | Current Posture |`,
535
- `| :--- | :--- | :--- | :--- |`,
536
- ...p[12].data.map((c) => `| ${c.cluster} | ${c.addressable_monthly_searches.toLocaleString()} | ${c.commercial_intent.toUpperCase()} | ${c.current_coverage} |`),
764
+ ];
765
+
766
+ const demandClusters = Array.isArray(p[12].data) ? p[12].data : (p[12].data?.clusters || []);
767
+ if (demandClusters.length > 0) {
768
+ lines.push(
769
+ `| Query Cluster | Addressable Volume | Commercial Intent | Current Posture |`,
770
+ `| :--- | :--- | :--- | :--- |`,
771
+ ...demandClusters.map((c) => `| ${escapeMarkdownTableCell(c.cluster)} | ${(c.addressable_monthly_searches || 0).toLocaleString()} | ${escapeMarkdownTableCell(c.commercial_intent || '').toUpperCase()} | ${escapeMarkdownTableCell(c.current_coverage || '')} |`)
772
+ );
773
+ } else {
774
+ lines.push(`- *Search demand telemetry not observed. External query volume integration required.*`);
775
+ }
776
+
777
+ lines.push(
537
778
  ``,
538
779
  `## 13. Search-to-Conversion Impact`,
539
- `- **Organic Conversion Rate**: ${p[13].data.organic_conversion_rate_pct}%`,
540
- `- **Attributed Pipeline MRR**: **${p[13].data.pipeline_attributed_mrr}**`,
541
- `- **Assisted Conversions Share**: ${p[13].data.assisted_conversions_share_pct}%`,
780
+ `- **Organic Conversion Rate**: ${formatVal(p[13].data.organic_conversion_rate_pct, '%')}`,
781
+ `- **Attributed Pipeline MRR**: **${formatVal(p[13].data.pipeline_attributed_mrr)}**`,
782
+ `- **Assisted Conversions Share**: ${formatVal(p[13].data.assisted_conversions_share_pct, '%')}`,
542
783
  ``,
543
784
  `## 14. Measurement Integrity & Analytics Instrumentation`,
544
- `- **GA4 & GSC**: ${p[14].data.ga4_instrumentation.toUpperCase()}`,
545
- `- **Consent Mode Data Loss**: ~${p[14].data.consent_mode_v2_loss_pct}%`,
546
- `- **Limitations**: ${p[14].data.known_limitations.join('; ')}`,
785
+ `- **GA4 & GSC**: ${formatVal(p[14].data.ga4_instrumentation).toUpperCase()} / ${formatVal(p[14].data.gsc_property_binding).toUpperCase()}`,
786
+ `- **Consent Mode Data Loss**: ${formatVal(p[14].data.consent_mode_v2_loss_pct, '%')}`,
787
+ `- **Limitations**: ${(p[14].data.known_limitations || []).map((l) => sanitizeForMarkdown(l)).join('; ')}`,
547
788
  ``,
548
789
  `## 15. Verified Evidence Register (Traceability Engine)`,
549
790
  `| Evidence ID | Source | Methodology | Confidence | Observation Date |`,
550
791
  `| :--- | :--- | :--- | :--- | :--- |`,
551
- ...report.evidence_register.map((e) => `| **${e.evidence_id}** | \`${e.source}\` | ${e.methodology.slice(0, 50)}... | \`${e.confidence_level}\` | ${e.observation_date} |`),
792
+ ...report.evidence_register.map((e) => `| **${e.evidence_id}** | \`${escapeMarkdownTableCell(e.source)}\` | ${escapeMarkdownTableCell((e.methodology || '').slice(0, 50))}... | \`${e.confidence_level}\` | ${e.observation_date} |`),
552
793
  ``,
553
794
  `## 16. Strategic Risk & Algorithmic Dependency Register`,
554
795
  `| Risk ID | Category | Severity | Description | Mitigation |`,
555
796
  `| :--- | :--- | :--- | :--- | :--- |`,
556
- ...p[16].data.map((r) => `| **${r.risk_id}** | \`${r.category}\` | **${r.severity.toUpperCase()}** | ${r.description} | ${r.mitigation} |`),
797
+ ...(p[16].data || []).map((r) => `| **${r.risk_id}** | \`${r.category}\` | **${r.severity.toUpperCase()}** | ${escapeMarkdownTableCell(r.description)} | ${escapeMarkdownTableCell(r.mitigation)} |`),
557
798
  ``,
558
799
  `## 17. ICE-BV Prioritization Matrix`,
559
800
  `- **Quick Wins Available**: ${p[17].data.summary?.quick_wins_count || 0}`,
@@ -561,9 +802,9 @@ export function renderSearchReportMarkdown(report) {
561
802
  `- **Total Prioritized Initiatives**: ${p[17].data.total_scored || 0}`,
562
803
  ``,
563
804
  `## 18. 30 / 90 / 180-Day Strategic Execution Roadmap`,
564
- ...p[18].data.horizons.map((h) => `### ${h.label}\n- **Objective**: ${h.objective}\n- **Focus Areas**: ${(h.focus_areas || []).join('; ')}\n- **Exit Criteria**: ${(h.exit_criteria || []).join('; ')}`),
565
- ``,
566
- ];
805
+ ...(p[18].data.horizons || []).map((h) => `### ${sanitizeForMarkdown(h.label)}\n- **Objective**: ${sanitizeForMarkdown(h.objective)}\n- **Focus Areas**: ${(h.focus_areas || []).map((f) => sanitizeForMarkdown(f)).join('; ')}\n- **Exit Criteria**: ${(h.exit_criteria || []).map((e) => sanitizeForMarkdown(e)).join('; ')}`),
806
+ ``
807
+ );
567
808
 
568
809
  return lines.join('\n');
569
810
  }
@@ -577,7 +818,7 @@ export function renderSearchReportHtml(report) {
577
818
  <html lang="en">
578
819
  <head>
579
820
  <meta charset="UTF-8">
580
- <title>${report.report_title} - ${report.client_name}</title>
821
+ <title>${report.report_title} - ${escapeHtml(report.client_name)}</title>
581
822
  <style>
582
823
  :root { --bg: #0b0f19; --card: #151d30; --border: #1e293b; --text: #f8fafc; --muted: #94a3b8; --accent: #38bdf8; --danger: #ef4444; --warning: #f59e0b; --success: #10b981; }
583
824
  body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: var(--bg); color: var(--text); line-height: 1.6; margin: 0; padding: 40px 20px; }
@@ -604,51 +845,58 @@ export function renderSearchReportHtml(report) {
604
845
  <div class="container">
605
846
  <header>
606
847
  <h1>${report.report_title}</h1>
607
- <div class="meta">Client: <strong>${report.client_name}</strong> | Target: <strong>${report.target_domain}</strong> | Generated: ${report.generated_at.split('T')[0]}</div>
848
+ <div class="meta">
849
+ Client: <strong>${escapeHtml(report.client_name)}</strong> &bull;
850
+ Target: <strong>${escapeHtml(report.target_domain)}</strong> &bull;
851
+ Generated: <strong>${escapeHtml(report.generated_at)}</strong> &bull;
852
+ Mode: <strong>${escapeHtml(report.generation_provenance.generation_mode)}</strong>
853
+ </div>
608
854
  </header>
609
855
 
610
856
  <div class="disclosure">
611
- <strong>Enterprise Governance Premise:</strong> This executive briefing provides observable technical discovery infrastructure, AEO extraction posture, and controlled citation measurements. Every conclusion is bound to an underlying record in the Evidence Register. No search engine ranking, AI citation, or conversion outcome is guaranteed.
857
+ <strong>Enterprise Operating Premise:</strong> This briefing is strictly bounded by deterministic DOM analysis, observable Core Web Vitals lab heuristics, and empirical citation observation probes. No rankings or conversions are promised or implied.
612
858
  </div>
613
859
 
614
860
  <div class="grid">
615
- <div class="card"><div>Overall Search & AEO Readiness</div><div class="card-num">${report.overall_readiness_score}/100</div></div>
616
- <div class="card"><div>E-E-A-T Score</div><div class="card-num">${p[7].data.overall_eeat_score}/5.0</div></div>
617
- <div class="card"><div>AI Overview Inclusion</div><div class="card-num">${p[1].data.google_ai_overviews.inclusion_rate_pct}%</div></div>
618
- <div class="card"><div>1st-Party Citation Share</div><div class="card-num">${p[10].data.first_party_citation_share_pct}%</div></div>
619
- </div>
620
-
621
- <h2 class="section-title">Executive Decision Summary (Separated Epistemology)</h2>
622
- <div class="card">
623
- <h3 style="color:var(--success); margin-top:0;">Confirmed Findings (Deterministic Observations)</h3>
624
- <ul>${report.decision_summary.confirmed_findings.map((f) => `<li>${f}</li>`).join('')}</ul>
625
-
626
- <h3 style="color:var(--accent);">Inferred Opportunities (Modeled Projections)</h3>
627
- <ul>${report.decision_summary.inferred_opportunities.map((o) => `<li>${o}</li>`).join('')}</ul>
628
-
629
- <h3 style="color:var(--warning);">Unresolved Unknowns (Measurement Gaps)</h3>
630
- <ul>${report.decision_summary.unresolved_unknowns.map((u) => `<li>${u}</li>`).join('')}</ul>
631
-
632
- <h3 style="color:var(--danger);">Strategic Risks & Platform Dependencies</h3>
633
- <ul>${report.decision_summary.strategic_risks.map((r) => `<li>${r}</li>`).join('')}</ul>
634
-
635
- <h3 style="color:#c084fc;">Actions Requiring Leadership Approval</h3>
636
- <ul>${report.decision_summary.actions_requiring_leadership_approval.map((a) => `<li>${a}</li>`).join('')}</ul>
861
+ <div class="card">
862
+ <div style="font-size:12px; color:var(--muted); text-transform:uppercase;">Composite Search Readiness</div>
863
+ <div class="card-num">${report.overall_readiness_score} / 100</div>
864
+ <span class="badge ${report.overall_readiness_score >= 70 ? 'badge-success' : 'badge-warning'}">${report.overall_readiness_score >= 70 ? 'Optimal' : 'Needs Optimization'}</span>
865
+ </div>
866
+ <div class="card">
867
+ <div style="font-size:12px; color:var(--muted); text-transform:uppercase;">AEO Direct Extraction</div>
868
+ <div class="card-num">${formatVal(p[5].data.readiness_score)}</div>
869
+ <span class="badge badge-success">${formatVal(p[5].data.status).toUpperCase()}</span>
870
+ </div>
871
+ <div class="card">
872
+ <div style="font-size:12px; color:var(--muted); text-transform:uppercase;">E-E-A-T Quality Score</div>
873
+ <div class="card-num">${formatVal(p[7].data.overall_eeat_score)} / 5.0</div>
874
+ <span class="badge badge-success">Evaluated</span>
875
+ </div>
876
+ <div class="card">
877
+ <div style="font-size:12px; color:var(--muted); text-transform:uppercase;">Core Web Vitals Blockers</div>
878
+ <div class="card-num">${formatVal(p[2].data.core_web_vitals?.render_blocking_scripts)}</div>
879
+ <span class="badge ${p[2].data.core_web_vitals?.render_blocking_scripts === 0 ? 'badge-success' : 'badge-danger'}">${p[2].data.core_web_vitals?.render_blocking_scripts === 0 ? 'Clean' : 'Action Required'}</span>
880
+ </div>
637
881
  </div>
638
882
 
639
- <h2 class="section-title">Evidence Register Traceability Engine</h2>
883
+ <h2 class="section-title">Verified Evidence Register (${report.evidence_register.length} Observations)</h2>
640
884
  <table>
641
885
  <thead>
642
- <tr><th>Evidence ID</th><th>Source Channel</th><th>Observation Date</th><th>Methodology</th><th>Confidence Level</th></tr>
886
+ <tr>
887
+ <th>Evidence ID</th>
888
+ <th>Source Domain / Trace</th>
889
+ <th>Methodology</th>
890
+ <th>Confidence</th>
891
+ </tr>
643
892
  </thead>
644
893
  <tbody>
645
894
  ${report.evidence_register.map((e) => `
646
895
  <tr>
647
- <td><span class="evidence-ref">${e.evidence_id}</span></td>
648
- <td><code>${e.source}</code></td>
649
- <td>${e.observation_date}</td>
650
- <td>${e.methodology}</td>
651
- <td><span class="badge ${e.confidence_level === 'deterministic' ? 'badge-success' : 'badge-warning'}">${e.confidence_level}</span></td>
896
+ <td><span class="evidence-ref">${escapeHtml(e.evidence_id)}</span></td>
897
+ <td><code>${escapeHtml(e.source)}</code></td>
898
+ <td>${escapeHtml(e.methodology)}</td>
899
+ <td><span class="badge ${e.confidence_level === 'deterministic' ? 'badge-success' : 'badge-warning'}">${escapeHtml(e.confidence_level)}</span></td>
652
900
  </tr>
653
901
  `).join('')}
654
902
  </tbody>
@@ -656,11 +904,11 @@ export function renderSearchReportHtml(report) {
656
904
 
657
905
  <h2 class="section-title">30 / 90 / 180-Day Execution Roadmap</h2>
658
906
  <div class="grid">
659
- ${p[18].data.horizons.map((h) => `
907
+ ${(p[18].data.horizons || []).map((h) => `
660
908
  <div class="card">
661
- <div class="badge badge-success">${h.horizon.toUpperCase()}</div>
662
- <h4 style="margin:8px 0;">${h.label}</h4>
663
- <div style="font-size:12px; font-weight:bold; color:var(--accent);">Target Exit Criteria: ${h.exit_criteria?.[0] || 'Operational Reliability'}</div>
909
+ <div class="badge badge-success">${escapeHtml((h.horizon || '').toUpperCase())}</div>
910
+ <h4 style="margin:8px 0;">${escapeHtml(h.label)}</h4>
911
+ <div style="font-size:12px; font-weight:bold; color:var(--accent);">Target Exit Criteria: ${escapeHtml(h.exit_criteria?.[0] || 'Operational Reliability')}</div>
664
912
  </div>
665
913
  `).join('')}
666
914
  </div>