@nebulacomponents/citable 1.15.1 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (264) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +18 -5
  3. package/dist/universal/.agents/skills/citable/SKILL.md +51 -1
  4. package/dist/universal/.agents/skills/citable/VERSION +1 -1
  5. package/dist/universal/.agents/skills/citable/commands/README.md +3 -1
  6. package/dist/universal/.agents/skills/citable/commands/governance.md +12 -0
  7. package/dist/universal/.agents/skills/citable/commands/measurement.md +20 -0
  8. package/dist/universal/.agents/skills/citable/commands/reporting.md +16 -0
  9. package/dist/universal/.agents/skills/citable/manifest.json +17 -12
  10. package/dist/universal/.agents/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
  11. package/dist/universal/.agents/skills/citable/schemas/experiment.schema.json +1 -1
  12. package/dist/universal/.agents/skills/citable/schemas/finding.schema.json +69 -0
  13. package/dist/universal/.agents/skills/citable/schemas/fleet.schema.json +93 -0
  14. package/dist/universal/.agents/skills/citable/schemas/funnel.schema.json +116 -0
  15. package/dist/universal/.agents/skills/citable/schemas/mcp-transport-envelope.schema.json +244 -0
  16. package/dist/universal/.agents/skills/citable/schemas/observation.schema.json +1 -1
  17. package/dist/universal/.agents/skills/citable/schemas/remediation-verification.schema.json +98 -0
  18. package/dist/universal/.claude/agents/citable/manifest.json +2 -2
  19. package/dist/universal/.claude/skills/citable/SKILL.md +51 -1
  20. package/dist/universal/.claude/skills/citable/VERSION +1 -1
  21. package/dist/universal/.claude/skills/citable/commands/README.md +3 -1
  22. package/dist/universal/.claude/skills/citable/commands/governance.md +12 -0
  23. package/dist/universal/.claude/skills/citable/commands/measurement.md +20 -0
  24. package/dist/universal/.claude/skills/citable/commands/reporting.md +16 -0
  25. package/dist/universal/.claude/skills/citable/manifest.json +17 -12
  26. package/dist/universal/.claude/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
  27. package/dist/universal/.claude/skills/citable/schemas/experiment.schema.json +1 -1
  28. package/dist/universal/.claude/skills/citable/schemas/finding.schema.json +69 -0
  29. package/dist/universal/.claude/skills/citable/schemas/fleet.schema.json +93 -0
  30. package/dist/universal/.claude/skills/citable/schemas/funnel.schema.json +116 -0
  31. package/dist/universal/.claude/skills/citable/schemas/mcp-transport-envelope.schema.json +244 -0
  32. package/dist/universal/.claude/skills/citable/schemas/observation.schema.json +1 -1
  33. package/dist/universal/.claude/skills/citable/schemas/remediation-verification.schema.json +98 -0
  34. package/dist/universal/.cursor/skills/citable/SKILL.md +51 -1
  35. package/dist/universal/.cursor/skills/citable/VERSION +1 -1
  36. package/dist/universal/.cursor/skills/citable/commands/README.md +3 -1
  37. package/dist/universal/.cursor/skills/citable/commands/governance.md +12 -0
  38. package/dist/universal/.cursor/skills/citable/commands/measurement.md +20 -0
  39. package/dist/universal/.cursor/skills/citable/commands/reporting.md +16 -0
  40. package/dist/universal/.cursor/skills/citable/manifest.json +17 -12
  41. package/dist/universal/.cursor/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
  42. package/dist/universal/.cursor/skills/citable/schemas/experiment.schema.json +1 -1
  43. package/dist/universal/.cursor/skills/citable/schemas/finding.schema.json +69 -0
  44. package/dist/universal/.cursor/skills/citable/schemas/fleet.schema.json +93 -0
  45. package/dist/universal/.cursor/skills/citable/schemas/funnel.schema.json +116 -0
  46. package/dist/universal/.cursor/skills/citable/schemas/mcp-transport-envelope.schema.json +244 -0
  47. package/dist/universal/.cursor/skills/citable/schemas/observation.schema.json +1 -1
  48. package/dist/universal/.cursor/skills/citable/schemas/remediation-verification.schema.json +98 -0
  49. package/dist/universal/.gemini/skills/citable/SKILL.md +51 -1
  50. package/dist/universal/.gemini/skills/citable/VERSION +1 -1
  51. package/dist/universal/.gemini/skills/citable/commands/README.md +3 -1
  52. package/dist/universal/.gemini/skills/citable/commands/governance.md +12 -0
  53. package/dist/universal/.gemini/skills/citable/commands/measurement.md +20 -0
  54. package/dist/universal/.gemini/skills/citable/commands/reporting.md +16 -0
  55. package/dist/universal/.gemini/skills/citable/manifest.json +17 -12
  56. package/dist/universal/.gemini/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
  57. package/dist/universal/.gemini/skills/citable/schemas/experiment.schema.json +1 -1
  58. package/dist/universal/.gemini/skills/citable/schemas/finding.schema.json +69 -0
  59. package/dist/universal/.gemini/skills/citable/schemas/fleet.schema.json +93 -0
  60. package/dist/universal/.gemini/skills/citable/schemas/funnel.schema.json +116 -0
  61. package/dist/universal/.gemini/skills/citable/schemas/mcp-transport-envelope.schema.json +244 -0
  62. package/dist/universal/.gemini/skills/citable/schemas/observation.schema.json +1 -1
  63. package/dist/universal/.gemini/skills/citable/schemas/remediation-verification.schema.json +98 -0
  64. package/dist/universal/.github/skills/citable/SKILL.md +51 -1
  65. package/dist/universal/.github/skills/citable/VERSION +1 -1
  66. package/dist/universal/.github/skills/citable/commands/README.md +3 -1
  67. package/dist/universal/.github/skills/citable/commands/governance.md +12 -0
  68. package/dist/universal/.github/skills/citable/commands/measurement.md +20 -0
  69. package/dist/universal/.github/skills/citable/commands/reporting.md +16 -0
  70. package/dist/universal/.github/skills/citable/manifest.json +17 -12
  71. package/dist/universal/.github/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
  72. package/dist/universal/.github/skills/citable/schemas/experiment.schema.json +1 -1
  73. package/dist/universal/.github/skills/citable/schemas/finding.schema.json +69 -0
  74. package/dist/universal/.github/skills/citable/schemas/fleet.schema.json +93 -0
  75. package/dist/universal/.github/skills/citable/schemas/funnel.schema.json +116 -0
  76. package/dist/universal/.github/skills/citable/schemas/mcp-transport-envelope.schema.json +244 -0
  77. package/dist/universal/.github/skills/citable/schemas/observation.schema.json +1 -1
  78. package/dist/universal/.github/skills/citable/schemas/remediation-verification.schema.json +98 -0
  79. package/dist/universal/.kiro/skills/citable/SKILL.md +51 -1
  80. package/dist/universal/.kiro/skills/citable/VERSION +1 -1
  81. package/dist/universal/.kiro/skills/citable/commands/README.md +3 -1
  82. package/dist/universal/.kiro/skills/citable/commands/governance.md +12 -0
  83. package/dist/universal/.kiro/skills/citable/commands/measurement.md +20 -0
  84. package/dist/universal/.kiro/skills/citable/commands/reporting.md +16 -0
  85. package/dist/universal/.kiro/skills/citable/manifest.json +17 -12
  86. package/dist/universal/.kiro/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
  87. package/dist/universal/.kiro/skills/citable/schemas/experiment.schema.json +1 -1
  88. package/dist/universal/.kiro/skills/citable/schemas/finding.schema.json +69 -0
  89. package/dist/universal/.kiro/skills/citable/schemas/fleet.schema.json +93 -0
  90. package/dist/universal/.kiro/skills/citable/schemas/funnel.schema.json +116 -0
  91. package/dist/universal/.kiro/skills/citable/schemas/mcp-transport-envelope.schema.json +244 -0
  92. package/dist/universal/.kiro/skills/citable/schemas/observation.schema.json +1 -1
  93. package/dist/universal/.kiro/skills/citable/schemas/remediation-verification.schema.json +98 -0
  94. package/dist/universal/.opencode/skills/citable/SKILL.md +51 -1
  95. package/dist/universal/.opencode/skills/citable/VERSION +1 -1
  96. package/dist/universal/.opencode/skills/citable/commands/README.md +3 -1
  97. package/dist/universal/.opencode/skills/citable/commands/governance.md +12 -0
  98. package/dist/universal/.opencode/skills/citable/commands/measurement.md +20 -0
  99. package/dist/universal/.opencode/skills/citable/commands/reporting.md +16 -0
  100. package/dist/universal/.opencode/skills/citable/manifest.json +17 -12
  101. package/dist/universal/.opencode/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
  102. package/dist/universal/.opencode/skills/citable/schemas/experiment.schema.json +1 -1
  103. package/dist/universal/.opencode/skills/citable/schemas/finding.schema.json +69 -0
  104. package/dist/universal/.opencode/skills/citable/schemas/fleet.schema.json +93 -0
  105. package/dist/universal/.opencode/skills/citable/schemas/funnel.schema.json +116 -0
  106. package/dist/universal/.opencode/skills/citable/schemas/mcp-transport-envelope.schema.json +244 -0
  107. package/dist/universal/.opencode/skills/citable/schemas/observation.schema.json +1 -1
  108. package/dist/universal/.opencode/skills/citable/schemas/remediation-verification.schema.json +98 -0
  109. package/dist/universal/.pi/agent/skills/citable/SKILL.md +51 -1
  110. package/dist/universal/.pi/agent/skills/citable/VERSION +1 -1
  111. package/dist/universal/.pi/agent/skills/citable/commands/README.md +3 -1
  112. package/dist/universal/.pi/agent/skills/citable/commands/governance.md +12 -0
  113. package/dist/universal/.pi/agent/skills/citable/commands/measurement.md +20 -0
  114. package/dist/universal/.pi/agent/skills/citable/commands/reporting.md +16 -0
  115. package/dist/universal/.pi/agent/skills/citable/manifest.json +17 -12
  116. package/dist/universal/.pi/agent/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
  117. package/dist/universal/.pi/agent/skills/citable/schemas/experiment.schema.json +1 -1
  118. package/dist/universal/.pi/agent/skills/citable/schemas/finding.schema.json +69 -0
  119. package/dist/universal/.pi/agent/skills/citable/schemas/fleet.schema.json +93 -0
  120. package/dist/universal/.pi/agent/skills/citable/schemas/funnel.schema.json +116 -0
  121. package/dist/universal/.pi/agent/skills/citable/schemas/mcp-transport-envelope.schema.json +244 -0
  122. package/dist/universal/.pi/agent/skills/citable/schemas/observation.schema.json +1 -1
  123. package/dist/universal/.pi/agent/skills/citable/schemas/remediation-verification.schema.json +98 -0
  124. package/dist/universal/.qoder/skills/citable/SKILL.md +51 -1
  125. package/dist/universal/.qoder/skills/citable/VERSION +1 -1
  126. package/dist/universal/.qoder/skills/citable/commands/README.md +3 -1
  127. package/dist/universal/.qoder/skills/citable/commands/governance.md +12 -0
  128. package/dist/universal/.qoder/skills/citable/commands/measurement.md +20 -0
  129. package/dist/universal/.qoder/skills/citable/commands/reporting.md +16 -0
  130. package/dist/universal/.qoder/skills/citable/manifest.json +17 -12
  131. package/dist/universal/.qoder/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
  132. package/dist/universal/.qoder/skills/citable/schemas/experiment.schema.json +1 -1
  133. package/dist/universal/.qoder/skills/citable/schemas/finding.schema.json +69 -0
  134. package/dist/universal/.qoder/skills/citable/schemas/fleet.schema.json +93 -0
  135. package/dist/universal/.qoder/skills/citable/schemas/funnel.schema.json +116 -0
  136. package/dist/universal/.qoder/skills/citable/schemas/mcp-transport-envelope.schema.json +244 -0
  137. package/dist/universal/.qoder/skills/citable/schemas/observation.schema.json +1 -1
  138. package/dist/universal/.qoder/skills/citable/schemas/remediation-verification.schema.json +98 -0
  139. package/dist/universal/.rovodev/skills/citable/SKILL.md +51 -1
  140. package/dist/universal/.rovodev/skills/citable/VERSION +1 -1
  141. package/dist/universal/.rovodev/skills/citable/commands/README.md +3 -1
  142. package/dist/universal/.rovodev/skills/citable/commands/governance.md +12 -0
  143. package/dist/universal/.rovodev/skills/citable/commands/measurement.md +20 -0
  144. package/dist/universal/.rovodev/skills/citable/commands/reporting.md +16 -0
  145. package/dist/universal/.rovodev/skills/citable/manifest.json +17 -12
  146. package/dist/universal/.rovodev/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
  147. package/dist/universal/.rovodev/skills/citable/schemas/experiment.schema.json +1 -1
  148. package/dist/universal/.rovodev/skills/citable/schemas/finding.schema.json +69 -0
  149. package/dist/universal/.rovodev/skills/citable/schemas/fleet.schema.json +93 -0
  150. package/dist/universal/.rovodev/skills/citable/schemas/funnel.schema.json +116 -0
  151. package/dist/universal/.rovodev/skills/citable/schemas/mcp-transport-envelope.schema.json +244 -0
  152. package/dist/universal/.rovodev/skills/citable/schemas/observation.schema.json +1 -1
  153. package/dist/universal/.rovodev/skills/citable/schemas/remediation-verification.schema.json +98 -0
  154. package/dist/universal/.trae/skills/citable/SKILL.md +51 -1
  155. package/dist/universal/.trae/skills/citable/VERSION +1 -1
  156. package/dist/universal/.trae/skills/citable/commands/README.md +3 -1
  157. package/dist/universal/.trae/skills/citable/commands/governance.md +12 -0
  158. package/dist/universal/.trae/skills/citable/commands/measurement.md +20 -0
  159. package/dist/universal/.trae/skills/citable/commands/reporting.md +16 -0
  160. package/dist/universal/.trae/skills/citable/manifest.json +17 -12
  161. package/dist/universal/.trae/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
  162. package/dist/universal/.trae/skills/citable/schemas/experiment.schema.json +1 -1
  163. package/dist/universal/.trae/skills/citable/schemas/finding.schema.json +69 -0
  164. package/dist/universal/.trae/skills/citable/schemas/fleet.schema.json +93 -0
  165. package/dist/universal/.trae/skills/citable/schemas/funnel.schema.json +116 -0
  166. package/dist/universal/.trae/skills/citable/schemas/mcp-transport-envelope.schema.json +244 -0
  167. package/dist/universal/.trae/skills/citable/schemas/observation.schema.json +1 -1
  168. package/dist/universal/.trae/skills/citable/schemas/remediation-verification.schema.json +98 -0
  169. package/dist/universal/.trae-cn/skills/citable/SKILL.md +51 -1
  170. package/dist/universal/.trae-cn/skills/citable/VERSION +1 -1
  171. package/dist/universal/.trae-cn/skills/citable/commands/README.md +3 -1
  172. package/dist/universal/.trae-cn/skills/citable/commands/governance.md +12 -0
  173. package/dist/universal/.trae-cn/skills/citable/commands/measurement.md +20 -0
  174. package/dist/universal/.trae-cn/skills/citable/commands/reporting.md +16 -0
  175. package/dist/universal/.trae-cn/skills/citable/manifest.json +17 -12
  176. package/dist/universal/.trae-cn/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
  177. package/dist/universal/.trae-cn/skills/citable/schemas/experiment.schema.json +1 -1
  178. package/dist/universal/.trae-cn/skills/citable/schemas/finding.schema.json +69 -0
  179. package/dist/universal/.trae-cn/skills/citable/schemas/fleet.schema.json +93 -0
  180. package/dist/universal/.trae-cn/skills/citable/schemas/funnel.schema.json +116 -0
  181. package/dist/universal/.trae-cn/skills/citable/schemas/mcp-transport-envelope.schema.json +244 -0
  182. package/dist/universal/.trae-cn/skills/citable/schemas/observation.schema.json +1 -1
  183. package/dist/universal/.trae-cn/skills/citable/schemas/remediation-verification.schema.json +98 -0
  184. package/dist/universal/manifest.json +208 -148
  185. package/package.json +1 -1
  186. package/schemas/cli-output-envelope.schema.json +30 -0
  187. package/schemas/experiment.schema.json +1 -1
  188. package/schemas/finding.schema.json +69 -0
  189. package/schemas/fleet.schema.json +93 -0
  190. package/schemas/funnel.schema.json +116 -0
  191. package/schemas/mcp-transport-envelope.schema.json +244 -0
  192. package/schemas/observation.schema.json +1 -1
  193. package/schemas/remediation-verification.schema.json +98 -0
  194. package/skill/SKILL.md +51 -1
  195. package/skill/commands/README.md +3 -1
  196. package/skill/commands/governance.md +12 -0
  197. package/skill/commands/measurement.md +20 -0
  198. package/skill/commands/reporting.md +16 -0
  199. package/src/analysis/saliency.js +119 -0
  200. package/src/cli/index.js +651 -13
  201. package/src/commands/attributeImpact.js +54 -0
  202. package/src/commands/audit.js +2 -2
  203. package/src/commands/ciWorkflow.js +63 -0
  204. package/src/commands/compatibility.js +205 -0
  205. package/src/commands/connect.js +3 -0
  206. package/src/commands/context.js +2 -1
  207. package/src/commands/edgeRules.js +196 -0
  208. package/src/commands/edgeSecurity.js +192 -0
  209. package/src/commands/exceptions.js +241 -0
  210. package/src/commands/experimentGuardrails.js +213 -0
  211. package/src/commands/fleet.js +104 -0
  212. package/src/commands/generateLlmsTxt.js +111 -0
  213. package/src/commands/goldenCorpus.js +155 -0
  214. package/src/commands/implementationKit.js +202 -0
  215. package/src/commands/inspectAeo.js +74 -0
  216. package/src/commands/inspectCro.js +142 -0
  217. package/src/commands/inspectGeo.js +52 -0
  218. package/src/commands/inspectSerp.js +317 -0
  219. package/src/commands/lintComponents.js +132 -0
  220. package/src/commands/monitor.js +138 -0
  221. package/src/commands/observe.js +6 -1
  222. package/src/commands/planExperiment.js +120 -0
  223. package/src/commands/previewCro.js +241 -0
  224. package/src/commands/probe.js +70 -0
  225. package/src/commands/remediate.js +461 -0
  226. package/src/commands/reportConsensus.js +48 -0
  227. package/src/commands/testFunnel.js +107 -0
  228. package/src/commands/verifyRemediation.js +204 -0
  229. package/src/commands/visualRegression.js +165 -0
  230. package/src/components/index.js +556 -0
  231. package/src/connectors/index.js +6 -1
  232. package/src/connectors/indexnow.js +431 -0
  233. package/src/connectors/mcp/allowlist.js +107 -0
  234. package/src/connectors/mcp/client.js +239 -0
  235. package/src/connectors/mcp/diagnostics.js +214 -0
  236. package/src/connectors/mcp/gscServer.js +155 -0
  237. package/src/connectors/mcp/http.js +130 -0
  238. package/src/connectors/mcp/index.js +7 -0
  239. package/src/connectors/mcp/pilot.js +138 -0
  240. package/src/connectors/mcp/pilotServer.js +80 -0
  241. package/src/connectors/mcp/sanitizer.js +38 -0
  242. package/src/connectors/mcp/stdio.js +178 -0
  243. package/src/crawler/ipRanges.js +161 -0
  244. package/src/detectors/agent.js +127 -0
  245. package/src/detectors/ans.js +282 -0
  246. package/src/detectors/crawl.js +46 -0
  247. package/src/detectors/cro.js +825 -0
  248. package/src/detectors/cwv.js +38 -1
  249. package/src/detectors/evd.js +101 -0
  250. package/src/detectors/framework.js +32 -2
  251. package/src/detectors/geoReco.js +408 -0
  252. package/src/detectors/index.js +4 -2
  253. package/src/detectors/link.js +112 -1
  254. package/src/detectors/page.js +101 -0
  255. package/src/detectors/schemaData.js +529 -0
  256. package/src/detectors/tech.js +306 -0
  257. package/src/extractor/page.js +186 -2
  258. package/src/extractor/site.js +9 -2
  259. package/src/installer/index.js +45 -2
  260. package/src/monitoring/alertDelivery.js +101 -7
  261. package/src/observations/attribution.js +217 -0
  262. package/src/registries/index.js +14 -0
  263. package/src/reporting/consensus.js +416 -0
  264. package/src/reporting/executiveExport.js +244 -0
@@ -18,6 +18,16 @@ export function filterAlerts(alerts = [], minSeverity = 'informational') {
18
18
 
19
19
  export function buildAlertPayload({ runA, runB, source = 'monitor', summary = {}, alerts = [], timestamp = nowIso() } = {}) {
20
20
  const critOrHigh = alerts.filter((a) => ['critical', 'high'].includes(a.severity)).length;
21
+ const regressionCount = alerts.filter((a) => [
22
+ 'index_loss',
23
+ 'canonical_regression',
24
+ 'citation_presence_change',
25
+ 'representation_divergence_observed',
26
+ 'state_change',
27
+ 'observation_missing',
28
+ 'share_of_voice_drop',
29
+ 'stance_regression',
30
+ ].includes(a.type)).length;
21
31
  return {
22
32
  event: 'citable.regression_alert',
23
33
  source,
@@ -26,6 +36,7 @@ export function buildAlertPayload({ runA, runB, source = 'monitor', summary = {}
26
36
  run_b: runB,
27
37
  summary: {
28
38
  total_alerts: alerts.length,
39
+ regression_count: regressionCount,
29
40
  critical_or_high: critOrHigh,
30
41
  ...summary,
31
42
  },
@@ -36,6 +47,7 @@ export function buildAlertPayload({ runA, runB, source = 'monitor', summary = {}
36
47
  export async function dispatchAlertWebhook(root, payload, {
37
48
  webhookUrl,
38
49
  headers = {},
50
+ secret = process.env.CITABLE_WEBHOOK_SECRET,
39
51
  timeoutMs = 10000,
40
52
  fetchImpl = globalThis.fetch,
41
53
  lookup,
@@ -50,7 +62,23 @@ export async function dispatchAlertWebhook(root, payload, {
50
62
  const payloadString = JSON.stringify(payload);
51
63
  const payloadHash = sha256(Buffer.from(payloadString));
52
64
  const deliveriesDir = path.join(root, '.citable', 'monitoring', 'deliveries');
65
+ const dispatchesDir = path.join(root, '.citable', 'monitoring', 'dispatches');
53
66
  const receiptFile = path.join(deliveriesDir, `${deliveryId}.json`);
67
+ const dispatchFile = path.join(dispatchesDir, `${deliveryId}.json`);
68
+
69
+ const requestHeaders = {
70
+ 'content-type': 'application/json',
71
+ 'x-citable-event': 'citable.regression_alert',
72
+ 'x-citable-delivery': deliveryId,
73
+ ...headers,
74
+ };
75
+
76
+ let signature = null;
77
+ if (secret) {
78
+ const hmac = crypto.createHmac('sha256', secret).update(payloadString).digest('hex');
79
+ signature = `sha256=${hmac}`;
80
+ requestHeaders['x-citable-signature'] = signature;
81
+ }
54
82
 
55
83
  const controller = new AbortController();
56
84
  const timer = setTimeout(() => controller.abort(), timeoutMs);
@@ -62,12 +90,7 @@ export async function dispatchAlertWebhook(root, payload, {
62
90
  try {
63
91
  const res = await fetchImpl(webhookUrl, {
64
92
  method: 'POST',
65
- headers: {
66
- 'content-type': 'application/json',
67
- 'x-citable-event': 'citable.regression_alert',
68
- 'x-citable-delivery': deliveryId,
69
- ...headers,
70
- },
93
+ headers: requestHeaders,
71
94
  body: payloadString,
72
95
  signal: controller.signal,
73
96
  });
@@ -89,10 +112,81 @@ export async function dispatchAlertWebhook(root, payload, {
89
112
  status_code: statusCode,
90
113
  success,
91
114
  payload_hash: payloadHash,
115
+ signature,
92
116
  error: errorMessage,
93
117
  summary: payload.summary,
94
118
  };
95
119
 
96
120
  writeJson(receiptFile, receipt);
97
- return { ...receipt, receipt_file: receiptFile };
121
+ writeJson(dispatchFile, receipt);
122
+ return { ...receipt, receipt_file: receiptFile, dispatch_file: dispatchFile };
123
+ }
124
+
125
+ export function formatSlackPayload(payload) {
126
+ const summary = payload.summary || {};
127
+ const alertLines = (payload.alerts || []).slice(0, 5).map((a) => `• *[${a.severity.toUpperCase()}]* ${a.message || a.detail || a.type}`);
128
+ return {
129
+ text: `Citable Alert: ${summary.total_alerts || 0} alerts detected (${summary.critical_or_high || 0} critical/high)`,
130
+ blocks: [
131
+ {
132
+ type: 'header',
133
+ text: { type: 'plain_text', text: '🚨 Citable Search & AEO Governance Alert', emoji: true },
134
+ },
135
+ {
136
+ type: 'section',
137
+ fields: [
138
+ { type: 'mrkdwn', text: `*Total Alerts:* ${summary.total_alerts || 0}` },
139
+ { type: 'mrkdwn', text: `*Critical / High:* ${summary.critical_or_high || 0}` },
140
+ { type: 'mrkdwn', text: `*Source:* ${payload.source || 'monitor'}` },
141
+ { type: 'mrkdwn', text: `*Generated At:* ${payload.generated_at}` },
142
+ ],
143
+ },
144
+ ...(alertLines.length > 0 ? [{
145
+ type: 'section',
146
+ text: { type: 'mrkdwn', text: alertLines.join('\n') },
147
+ }] : []),
148
+ ],
149
+ };
98
150
  }
151
+
152
+ export function formatTeamsPayload(payload) {
153
+ const summary = payload.summary || {};
154
+ return {
155
+ '@type': 'MessageCard',
156
+ '@context': 'http://schema.org/extensions',
157
+ summary: 'Citable Search & AEO Governance Alert',
158
+ themeColor: summary.critical_or_high > 0 ? 'D9381E' : '0076D7',
159
+ title: 'Citable Governance Alert',
160
+ sections: [
161
+ {
162
+ activityTitle: `Total Alerts: ${summary.total_alerts || 0} (${summary.critical_or_high || 0} critical/high)`,
163
+ facts: [
164
+ { name: 'Source', value: payload.source || 'monitor' },
165
+ { name: 'Timestamp', value: payload.generated_at },
166
+ ],
167
+ text: (payload.alerts || []).slice(0, 5).map((a) => `**[${a.severity.toUpperCase()}]** ${a.message || a.detail || a.type}`).join('\n\n'),
168
+ },
169
+ ],
170
+ };
171
+ }
172
+
173
+ export function formatDiscordPayload(payload) {
174
+ const summary = payload.summary || {};
175
+ return {
176
+ content: `🚨 **Citable Alert**: ${summary.total_alerts || 0} alerts detected`,
177
+ embeds: [
178
+ {
179
+ title: 'Citable Search & AEO Governance Alert',
180
+ color: summary.critical_or_high > 0 ? 14232606 : 3447003,
181
+ fields: [
182
+ { name: 'Total Alerts', value: String(summary.total_alerts || 0), inline: true },
183
+ { name: 'Critical/High', value: String(summary.critical_or_high || 0), inline: true },
184
+ { name: 'Source', value: payload.source || 'monitor', inline: true },
185
+ ],
186
+ description: (payload.alerts || []).slice(0, 5).map((a) => `• **[${a.severity.toUpperCase()}]** ${a.message || a.detail || a.type}`).join('\n'),
187
+ timestamp: payload.generated_at,
188
+ },
189
+ ],
190
+ };
191
+ }
192
+
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Citation Attribution and Claim Entailment Verification.
3
+ *
4
+ * Evaluates whether generative engine answers accurately reflect registered
5
+ * claim assertions or attribute distorted, negated, or contradicted claims to
6
+ * an entity or brand.
7
+ *
8
+ * Governed Invariants:
9
+ * - Does not guess or extrapolate; evaluates strictly against registered claims.
10
+ * - Distinguishes between supported claims, distorted claims, and unsupported claims.
11
+ * - Emits schema-validated attribution observation envelopes.
12
+ */
13
+
14
+ import { envelope, observationRun, readInput } from "./common.js";
15
+ import { matchClaim } from "./claimDiff.js";
16
+ import { substantiate } from "../commands/substantiate.js";
17
+ import { loadRegistries } from "../registries/index.js";
18
+
19
+ const NEGATION_PATTERNS = [
20
+ /\b(?:does\s+not|doesn't|cannot|can't|fails\s+to|lacks|missing|no\s+longer|unable\s+to|without|refuses\s+to|not\s+supported)\b/i,
21
+ ];
22
+
23
+ function splitSentences(text) {
24
+ if (!text) return [];
25
+ return String(text)
26
+ .replace(/([.?!])\s+/g, "$1|§|")
27
+ .split("|§|")
28
+ .map((s) => s.trim())
29
+ .filter((s) => s.length > 5);
30
+ }
31
+
32
+ function hasNegation(text) {
33
+ return NEGATION_PATTERNS.some((p) => p.test(text));
34
+ }
35
+
36
+ export function evaluateAnswerAttribution(answerText, entity, {
37
+ claims = [],
38
+ citationUrl = null,
39
+ assessmentByClaimId = new Map(),
40
+ reviewer = null,
41
+ } = {}) {
42
+ const names = [entity.canonical_name, ...(entity.aliases || [])].map((n) => n.toLowerCase());
43
+ const lowerAnswer = String(answerText || "").toLowerCase();
44
+ const mentioned = names.some((n) => lowerAnswer.includes(n));
45
+
46
+ if (!mentioned) {
47
+ return {
48
+ mentioned: false,
49
+ entity_id: entity.entity_id,
50
+ evaluations: [],
51
+ has_distorted_claims: false,
52
+ supported_count: 0,
53
+ distorted_count: 0,
54
+ unsupported_count: 0,
55
+ };
56
+ }
57
+
58
+ const sentences = splitSentences(answerText);
59
+ const evaluations = [];
60
+ const relevantClaims = claims.filter((c) => !c.entity || c.entity === entity.entity_id);
61
+
62
+ for (const sentence of sentences) {
63
+ const sentenceMentionsEntity = names.some((n) => sentence.toLowerCase().includes(n));
64
+ if (!sentenceMentionsEntity && sentences.length > 1) continue;
65
+
66
+ const matched = matchClaim(sentence, relevantClaims, citationUrl);
67
+ if (!matched) continue;
68
+
69
+ const assessment = assessmentByClaimId.get(matched.claim_id);
70
+ const claimContradicted = matched.status === "contradicted" ||
71
+ assessment?.outcome === "contradicted" ||
72
+ (matched.contradictory_sources && matched.contradictory_sources.length > 0);
73
+ const claimExpired = matched.status === "expired" || (matched.expires && new Date(matched.expires) < new Date());
74
+ const sentenceNegated = hasNegation(sentence);
75
+
76
+ let attributionStatus;
77
+ const reasons = [];
78
+
79
+ if (claimContradicted) {
80
+ attributionStatus = "distorted";
81
+ reasons.push(`Answer cites claim "${matched.claim_id}" which is contradicted in registry.`);
82
+ } else if (sentenceNegated) {
83
+ attributionStatus = "distorted";
84
+ reasons.push(`Answer negates registered capability declared in claim "${matched.claim_id}".`);
85
+ } else if (claimExpired) {
86
+ attributionStatus = "distorted";
87
+ reasons.push(`Answer asserts claim "${matched.claim_id}" which has expired (${matched.expires}).`);
88
+ } else if (matched.status === "unverified" || assessment?.outcome === "unsupported") {
89
+ attributionStatus = "unsupported";
90
+ reasons.push(`Answer attributes unverified claim "${matched.claim_id}".`);
91
+ } else {
92
+ attributionStatus = "supported";
93
+ reasons.push(`Answer accurately attributes verified claim "${matched.claim_id}".`);
94
+ }
95
+
96
+ // Check exclusion violations
97
+ if (matched.exclusions && matched.exclusions.length > 0) {
98
+ for (const exclusion of matched.exclusions) {
99
+ if (sentence.toLowerCase().includes(exclusion.toLowerCase())) {
100
+ attributionStatus = "distorted";
101
+ reasons.push(`Answer asserts feature listed under exclusions: "${exclusion}".`);
102
+ }
103
+ }
104
+ }
105
+
106
+ evaluations.push({
107
+ claim_id: matched.claim_id,
108
+ claim_text: matched.claim,
109
+ assertion_excerpt: sentence,
110
+ attribution_status: attributionStatus,
111
+ reasons,
112
+ });
113
+ }
114
+
115
+ const supportedCount = evaluations.filter((e) => e.attribution_status === "supported").length;
116
+ const distortedCount = evaluations.filter((e) => e.attribution_status === "distorted").length;
117
+ const unsupportedCount = evaluations.filter((e) => e.attribution_status === "unsupported").length;
118
+
119
+ return {
120
+ mentioned: true,
121
+ entity_id: entity.entity_id,
122
+ canonical_name: entity.canonical_name,
123
+ evaluations,
124
+ has_distorted_claims: distortedCount > 0,
125
+ supported_count: supportedCount,
126
+ distorted_count: distortedCount,
127
+ unsupported_count: unsupportedCount,
128
+ };
129
+ }
130
+
131
+ export async function observeAttribution(root, options = {}) {
132
+ const input = readInput(options.input);
133
+ const { registries, problems } = loadRegistries(root);
134
+ if (problems.length) throw new Error(`registry validation failed: ${problems.join("; ")}`);
135
+
136
+ const entities = (registries.entities?.entries || []).filter((e) => e.status !== "retired");
137
+ if (!entities.length) throw new Error("no active entities found in entity registry");
138
+
139
+ const claims = registries.claims?.entries || [];
140
+
141
+ // Build claim assessments
142
+ const assessmentMap = new Map();
143
+ try {
144
+ const subRes = substantiate(root);
145
+ for (const item of subRes.assessments || []) {
146
+ assessmentMap.set(item.claim_id, item);
147
+ }
148
+ } catch {}
149
+
150
+ const targetEntities = options.entity
151
+ ? entities.filter((e) => e.entity_id === options.entity || e.canonical_name.toLowerCase() === options.entity.toLowerCase())
152
+ : entities;
153
+
154
+ if (options.entity && !targetEntities.length) {
155
+ throw new Error(`entity not found: ${options.entity}`);
156
+ }
157
+
158
+ const doc = input.value;
159
+ const items = Array.isArray(doc)
160
+ ? doc
161
+ : doc.observations || doc.items || doc.answers || doc.prompts || [doc];
162
+
163
+ const observations = [];
164
+ for (const item of items) {
165
+ const answerText = item.answer_text || item.answer || item.text || item.data?.answer_text || "";
166
+ if (!answerText) continue;
167
+
168
+ const promptId = item.prompt_id || item.data?.prompt_id || "unknown";
169
+ const promptText = item.prompt_text || item.data?.prompt_text || null;
170
+ const engine = item.provider || item.engine || item.data?.provider || item.data?.engine || "unknown";
171
+ const citationUrl = item.citations?.[0]?.url || item.url || null;
172
+ const reviewer = options.reviewer || item.reviewer || item.evaluator || null;
173
+
174
+ for (const entity of targetEntities) {
175
+ const evaluation = evaluateAnswerAttribution(answerText, entity, {
176
+ claims,
177
+ citationUrl,
178
+ assessmentByClaimId: assessmentMap,
179
+ reviewer,
180
+ });
181
+
182
+ if (!evaluation.mentioned) continue;
183
+
184
+ const data = {
185
+ prompt_id: promptId,
186
+ prompt_text: promptText,
187
+ engine,
188
+ entity_id: entity.entity_id,
189
+ canonical_name: entity.canonical_name,
190
+ citation_url: citationUrl,
191
+ evaluations: evaluation.evaluations,
192
+ has_distorted_claims: evaluation.has_distorted_claims,
193
+ supported_count: evaluation.supported_count,
194
+ distorted_count: evaluation.distorted_count,
195
+ unsupported_count: evaluation.unsupported_count,
196
+ reviewer,
197
+ };
198
+
199
+ const raw = JSON.stringify({ item, evaluation });
200
+ observations.push(envelope("attribution", data, {
201
+ method: reviewer ? "human_review" : "static_analysis",
202
+ source: input.file,
203
+ state: evaluation.has_distorted_claims ? "review_required" : "observed",
204
+ confidence: reviewer ? "confirmed" : "medium",
205
+ raw,
206
+ limitations: [
207
+ "Claim attribution evaluation maps extracted generative assertions against registered claims.",
208
+ "Attribution findings require human review for authoritative legal or compliance determination.",
209
+ ],
210
+ }));
211
+ }
212
+ }
213
+
214
+ return observationRun(root, "observe attribution", input.file, observations, {
215
+ rawInputs: { attribution_input: input.raw },
216
+ });
217
+ }
@@ -33,6 +33,10 @@ export const REGISTRY_SPECS = [
33
33
  { file: 'assumptions.yaml', kind: 'assumptions', schema: 'assumption.schema.json', idField: 'assumption_id' },
34
34
  { file: 'scenarios.yaml', kind: 'scenarios', schema: 'scenario.schema.json', idField: 'scenario_id' },
35
35
  { file: 'initiatives.yaml', kind: 'initiatives', schema: 'initiative.schema.json', idField: 'initiative_id' },
36
+ // Conversion Rate Optimization suite — funnels
37
+ { file: 'funnels.yaml', kind: 'funnels', schema: 'funnel.schema.json', idField: 'funnel_id' },
38
+ // Multi-property fleet governance
39
+ { file: 'fleet.yaml', kind: 'fleet', schema: 'fleet.schema.json', idField: 'property_id' },
36
40
  ];
37
41
 
38
42
  export function contextDir(root) {
@@ -232,6 +236,16 @@ export function checkReferentialIntegrity(registries) {
232
236
  }
233
237
  }
234
238
 
239
+ // funnels: step.page_id should reference known pages
240
+ for (const fn of registries.funnels?.entries || []) {
241
+ const prefix = `funnels/${fn.funnel_id}`;
242
+ for (const step of fn.steps || []) {
243
+ if (step.page_id && !ids.pages?.has(step.page_id)) {
244
+ problems.push(`${prefix}: step "${step.step_id}" references unknown page id "${step.page_id}"`);
245
+ }
246
+ }
247
+ }
248
+
235
249
  return problems;
236
250
  }
237
251