@nebulacomponents/citable 1.18.2 → 1.20.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 (298) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/README.md +12 -1
  3. package/dist/universal/.agents/skills/citable/SKILL.md +11 -2
  4. package/dist/universal/.agents/skills/citable/VERSION +1 -1
  5. package/dist/universal/.agents/skills/citable/manifest.json +19 -13
  6. package/dist/universal/.agents/skills/citable/schemas/action-plan.schema.json +38 -1
  7. package/dist/universal/.agents/skills/citable/schemas/audit-coverage.schema.json +118 -0
  8. package/dist/universal/.agents/skills/citable/schemas/browser-evidence-plan.schema.json +186 -3
  9. package/dist/universal/.agents/skills/citable/schemas/browser-network-events.schema.json +142 -0
  10. package/dist/universal/.agents/skills/citable/schemas/browser-runtime-events.schema.json +112 -0
  11. package/dist/universal/.agents/skills/citable/schemas/browser-state-observations.schema.json +97 -0
  12. package/dist/universal/.agents/skills/citable/schemas/collection-result.schema.json +35 -0
  13. package/dist/universal/.agents/skills/citable/schemas/config.schema.json +2 -0
  14. package/dist/universal/.agents/skills/citable/schemas/cro-report.schema.json +8 -1
  15. package/dist/universal/.agents/skills/citable/schemas/finding.schema.json +105 -2
  16. package/dist/universal/.agents/skills/citable/schemas/remediation-verification.schema.json +12 -1
  17. package/dist/universal/.agents/skills/citable/schemas/run.schema.json +15 -2
  18. package/dist/universal/.agents/skills/citable/schemas/search-report.schema.json +8 -1
  19. package/dist/universal/.agents/skills/citable/schemas/summary.schema.json +41 -0
  20. package/dist/universal/.claude/agents/citable/manifest.json +2 -2
  21. package/dist/universal/.claude/skills/citable/SKILL.md +11 -2
  22. package/dist/universal/.claude/skills/citable/VERSION +1 -1
  23. package/dist/universal/.claude/skills/citable/manifest.json +19 -13
  24. package/dist/universal/.claude/skills/citable/schemas/action-plan.schema.json +38 -1
  25. package/dist/universal/.claude/skills/citable/schemas/audit-coverage.schema.json +118 -0
  26. package/dist/universal/.claude/skills/citable/schemas/browser-evidence-plan.schema.json +186 -3
  27. package/dist/universal/.claude/skills/citable/schemas/browser-network-events.schema.json +142 -0
  28. package/dist/universal/.claude/skills/citable/schemas/browser-runtime-events.schema.json +112 -0
  29. package/dist/universal/.claude/skills/citable/schemas/browser-state-observations.schema.json +97 -0
  30. package/dist/universal/.claude/skills/citable/schemas/collection-result.schema.json +35 -0
  31. package/dist/universal/.claude/skills/citable/schemas/config.schema.json +2 -0
  32. package/dist/universal/.claude/skills/citable/schemas/cro-report.schema.json +8 -1
  33. package/dist/universal/.claude/skills/citable/schemas/finding.schema.json +105 -2
  34. package/dist/universal/.claude/skills/citable/schemas/remediation-verification.schema.json +12 -1
  35. package/dist/universal/.claude/skills/citable/schemas/run.schema.json +15 -2
  36. package/dist/universal/.claude/skills/citable/schemas/search-report.schema.json +8 -1
  37. package/dist/universal/.claude/skills/citable/schemas/summary.schema.json +41 -0
  38. package/dist/universal/.cursor/skills/citable/SKILL.md +11 -2
  39. package/dist/universal/.cursor/skills/citable/VERSION +1 -1
  40. package/dist/universal/.cursor/skills/citable/manifest.json +19 -13
  41. package/dist/universal/.cursor/skills/citable/schemas/action-plan.schema.json +38 -1
  42. package/dist/universal/.cursor/skills/citable/schemas/audit-coverage.schema.json +118 -0
  43. package/dist/universal/.cursor/skills/citable/schemas/browser-evidence-plan.schema.json +186 -3
  44. package/dist/universal/.cursor/skills/citable/schemas/browser-network-events.schema.json +142 -0
  45. package/dist/universal/.cursor/skills/citable/schemas/browser-runtime-events.schema.json +112 -0
  46. package/dist/universal/.cursor/skills/citable/schemas/browser-state-observations.schema.json +97 -0
  47. package/dist/universal/.cursor/skills/citable/schemas/collection-result.schema.json +35 -0
  48. package/dist/universal/.cursor/skills/citable/schemas/config.schema.json +2 -0
  49. package/dist/universal/.cursor/skills/citable/schemas/cro-report.schema.json +8 -1
  50. package/dist/universal/.cursor/skills/citable/schemas/finding.schema.json +105 -2
  51. package/dist/universal/.cursor/skills/citable/schemas/remediation-verification.schema.json +12 -1
  52. package/dist/universal/.cursor/skills/citable/schemas/run.schema.json +15 -2
  53. package/dist/universal/.cursor/skills/citable/schemas/search-report.schema.json +8 -1
  54. package/dist/universal/.cursor/skills/citable/schemas/summary.schema.json +41 -0
  55. package/dist/universal/.gemini/skills/citable/SKILL.md +11 -2
  56. package/dist/universal/.gemini/skills/citable/VERSION +1 -1
  57. package/dist/universal/.gemini/skills/citable/manifest.json +19 -13
  58. package/dist/universal/.gemini/skills/citable/schemas/action-plan.schema.json +38 -1
  59. package/dist/universal/.gemini/skills/citable/schemas/audit-coverage.schema.json +118 -0
  60. package/dist/universal/.gemini/skills/citable/schemas/browser-evidence-plan.schema.json +186 -3
  61. package/dist/universal/.gemini/skills/citable/schemas/browser-network-events.schema.json +142 -0
  62. package/dist/universal/.gemini/skills/citable/schemas/browser-runtime-events.schema.json +112 -0
  63. package/dist/universal/.gemini/skills/citable/schemas/browser-state-observations.schema.json +97 -0
  64. package/dist/universal/.gemini/skills/citable/schemas/collection-result.schema.json +35 -0
  65. package/dist/universal/.gemini/skills/citable/schemas/config.schema.json +2 -0
  66. package/dist/universal/.gemini/skills/citable/schemas/cro-report.schema.json +8 -1
  67. package/dist/universal/.gemini/skills/citable/schemas/finding.schema.json +105 -2
  68. package/dist/universal/.gemini/skills/citable/schemas/remediation-verification.schema.json +12 -1
  69. package/dist/universal/.gemini/skills/citable/schemas/run.schema.json +15 -2
  70. package/dist/universal/.gemini/skills/citable/schemas/search-report.schema.json +8 -1
  71. package/dist/universal/.gemini/skills/citable/schemas/summary.schema.json +41 -0
  72. package/dist/universal/.github/skills/citable/SKILL.md +11 -2
  73. package/dist/universal/.github/skills/citable/VERSION +1 -1
  74. package/dist/universal/.github/skills/citable/manifest.json +19 -13
  75. package/dist/universal/.github/skills/citable/schemas/action-plan.schema.json +38 -1
  76. package/dist/universal/.github/skills/citable/schemas/audit-coverage.schema.json +118 -0
  77. package/dist/universal/.github/skills/citable/schemas/browser-evidence-plan.schema.json +186 -3
  78. package/dist/universal/.github/skills/citable/schemas/browser-network-events.schema.json +142 -0
  79. package/dist/universal/.github/skills/citable/schemas/browser-runtime-events.schema.json +112 -0
  80. package/dist/universal/.github/skills/citable/schemas/browser-state-observations.schema.json +97 -0
  81. package/dist/universal/.github/skills/citable/schemas/collection-result.schema.json +35 -0
  82. package/dist/universal/.github/skills/citable/schemas/config.schema.json +2 -0
  83. package/dist/universal/.github/skills/citable/schemas/cro-report.schema.json +8 -1
  84. package/dist/universal/.github/skills/citable/schemas/finding.schema.json +105 -2
  85. package/dist/universal/.github/skills/citable/schemas/remediation-verification.schema.json +12 -1
  86. package/dist/universal/.github/skills/citable/schemas/run.schema.json +15 -2
  87. package/dist/universal/.github/skills/citable/schemas/search-report.schema.json +8 -1
  88. package/dist/universal/.github/skills/citable/schemas/summary.schema.json +41 -0
  89. package/dist/universal/.kiro/skills/citable/SKILL.md +11 -2
  90. package/dist/universal/.kiro/skills/citable/VERSION +1 -1
  91. package/dist/universal/.kiro/skills/citable/manifest.json +19 -13
  92. package/dist/universal/.kiro/skills/citable/schemas/action-plan.schema.json +38 -1
  93. package/dist/universal/.kiro/skills/citable/schemas/audit-coverage.schema.json +118 -0
  94. package/dist/universal/.kiro/skills/citable/schemas/browser-evidence-plan.schema.json +186 -3
  95. package/dist/universal/.kiro/skills/citable/schemas/browser-network-events.schema.json +142 -0
  96. package/dist/universal/.kiro/skills/citable/schemas/browser-runtime-events.schema.json +112 -0
  97. package/dist/universal/.kiro/skills/citable/schemas/browser-state-observations.schema.json +97 -0
  98. package/dist/universal/.kiro/skills/citable/schemas/collection-result.schema.json +35 -0
  99. package/dist/universal/.kiro/skills/citable/schemas/config.schema.json +2 -0
  100. package/dist/universal/.kiro/skills/citable/schemas/cro-report.schema.json +8 -1
  101. package/dist/universal/.kiro/skills/citable/schemas/finding.schema.json +105 -2
  102. package/dist/universal/.kiro/skills/citable/schemas/remediation-verification.schema.json +12 -1
  103. package/dist/universal/.kiro/skills/citable/schemas/run.schema.json +15 -2
  104. package/dist/universal/.kiro/skills/citable/schemas/search-report.schema.json +8 -1
  105. package/dist/universal/.kiro/skills/citable/schemas/summary.schema.json +41 -0
  106. package/dist/universal/.opencode/skills/citable/SKILL.md +11 -2
  107. package/dist/universal/.opencode/skills/citable/VERSION +1 -1
  108. package/dist/universal/.opencode/skills/citable/manifest.json +19 -13
  109. package/dist/universal/.opencode/skills/citable/schemas/action-plan.schema.json +38 -1
  110. package/dist/universal/.opencode/skills/citable/schemas/audit-coverage.schema.json +118 -0
  111. package/dist/universal/.opencode/skills/citable/schemas/browser-evidence-plan.schema.json +186 -3
  112. package/dist/universal/.opencode/skills/citable/schemas/browser-network-events.schema.json +142 -0
  113. package/dist/universal/.opencode/skills/citable/schemas/browser-runtime-events.schema.json +112 -0
  114. package/dist/universal/.opencode/skills/citable/schemas/browser-state-observations.schema.json +97 -0
  115. package/dist/universal/.opencode/skills/citable/schemas/collection-result.schema.json +35 -0
  116. package/dist/universal/.opencode/skills/citable/schemas/config.schema.json +2 -0
  117. package/dist/universal/.opencode/skills/citable/schemas/cro-report.schema.json +8 -1
  118. package/dist/universal/.opencode/skills/citable/schemas/finding.schema.json +105 -2
  119. package/dist/universal/.opencode/skills/citable/schemas/remediation-verification.schema.json +12 -1
  120. package/dist/universal/.opencode/skills/citable/schemas/run.schema.json +15 -2
  121. package/dist/universal/.opencode/skills/citable/schemas/search-report.schema.json +8 -1
  122. package/dist/universal/.opencode/skills/citable/schemas/summary.schema.json +41 -0
  123. package/dist/universal/.pi/agent/skills/citable/SKILL.md +11 -2
  124. package/dist/universal/.pi/agent/skills/citable/VERSION +1 -1
  125. package/dist/universal/.pi/agent/skills/citable/manifest.json +19 -13
  126. package/dist/universal/.pi/agent/skills/citable/schemas/action-plan.schema.json +38 -1
  127. package/dist/universal/.pi/agent/skills/citable/schemas/audit-coverage.schema.json +118 -0
  128. package/dist/universal/.pi/agent/skills/citable/schemas/browser-evidence-plan.schema.json +186 -3
  129. package/dist/universal/.pi/agent/skills/citable/schemas/browser-network-events.schema.json +142 -0
  130. package/dist/universal/.pi/agent/skills/citable/schemas/browser-runtime-events.schema.json +112 -0
  131. package/dist/universal/.pi/agent/skills/citable/schemas/browser-state-observations.schema.json +97 -0
  132. package/dist/universal/.pi/agent/skills/citable/schemas/collection-result.schema.json +35 -0
  133. package/dist/universal/.pi/agent/skills/citable/schemas/config.schema.json +2 -0
  134. package/dist/universal/.pi/agent/skills/citable/schemas/cro-report.schema.json +8 -1
  135. package/dist/universal/.pi/agent/skills/citable/schemas/finding.schema.json +105 -2
  136. package/dist/universal/.pi/agent/skills/citable/schemas/remediation-verification.schema.json +12 -1
  137. package/dist/universal/.pi/agent/skills/citable/schemas/run.schema.json +15 -2
  138. package/dist/universal/.pi/agent/skills/citable/schemas/search-report.schema.json +8 -1
  139. package/dist/universal/.pi/agent/skills/citable/schemas/summary.schema.json +41 -0
  140. package/dist/universal/.qoder/skills/citable/SKILL.md +11 -2
  141. package/dist/universal/.qoder/skills/citable/VERSION +1 -1
  142. package/dist/universal/.qoder/skills/citable/manifest.json +19 -13
  143. package/dist/universal/.qoder/skills/citable/schemas/action-plan.schema.json +38 -1
  144. package/dist/universal/.qoder/skills/citable/schemas/audit-coverage.schema.json +118 -0
  145. package/dist/universal/.qoder/skills/citable/schemas/browser-evidence-plan.schema.json +186 -3
  146. package/dist/universal/.qoder/skills/citable/schemas/browser-network-events.schema.json +142 -0
  147. package/dist/universal/.qoder/skills/citable/schemas/browser-runtime-events.schema.json +112 -0
  148. package/dist/universal/.qoder/skills/citable/schemas/browser-state-observations.schema.json +97 -0
  149. package/dist/universal/.qoder/skills/citable/schemas/collection-result.schema.json +35 -0
  150. package/dist/universal/.qoder/skills/citable/schemas/config.schema.json +2 -0
  151. package/dist/universal/.qoder/skills/citable/schemas/cro-report.schema.json +8 -1
  152. package/dist/universal/.qoder/skills/citable/schemas/finding.schema.json +105 -2
  153. package/dist/universal/.qoder/skills/citable/schemas/remediation-verification.schema.json +12 -1
  154. package/dist/universal/.qoder/skills/citable/schemas/run.schema.json +15 -2
  155. package/dist/universal/.qoder/skills/citable/schemas/search-report.schema.json +8 -1
  156. package/dist/universal/.qoder/skills/citable/schemas/summary.schema.json +41 -0
  157. package/dist/universal/.rovodev/skills/citable/SKILL.md +11 -2
  158. package/dist/universal/.rovodev/skills/citable/VERSION +1 -1
  159. package/dist/universal/.rovodev/skills/citable/manifest.json +19 -13
  160. package/dist/universal/.rovodev/skills/citable/schemas/action-plan.schema.json +38 -1
  161. package/dist/universal/.rovodev/skills/citable/schemas/audit-coverage.schema.json +118 -0
  162. package/dist/universal/.rovodev/skills/citable/schemas/browser-evidence-plan.schema.json +186 -3
  163. package/dist/universal/.rovodev/skills/citable/schemas/browser-network-events.schema.json +142 -0
  164. package/dist/universal/.rovodev/skills/citable/schemas/browser-runtime-events.schema.json +112 -0
  165. package/dist/universal/.rovodev/skills/citable/schemas/browser-state-observations.schema.json +97 -0
  166. package/dist/universal/.rovodev/skills/citable/schemas/collection-result.schema.json +35 -0
  167. package/dist/universal/.rovodev/skills/citable/schemas/config.schema.json +2 -0
  168. package/dist/universal/.rovodev/skills/citable/schemas/cro-report.schema.json +8 -1
  169. package/dist/universal/.rovodev/skills/citable/schemas/finding.schema.json +105 -2
  170. package/dist/universal/.rovodev/skills/citable/schemas/remediation-verification.schema.json +12 -1
  171. package/dist/universal/.rovodev/skills/citable/schemas/run.schema.json +15 -2
  172. package/dist/universal/.rovodev/skills/citable/schemas/search-report.schema.json +8 -1
  173. package/dist/universal/.rovodev/skills/citable/schemas/summary.schema.json +41 -0
  174. package/dist/universal/.trae/skills/citable/SKILL.md +11 -2
  175. package/dist/universal/.trae/skills/citable/VERSION +1 -1
  176. package/dist/universal/.trae/skills/citable/manifest.json +19 -13
  177. package/dist/universal/.trae/skills/citable/schemas/action-plan.schema.json +38 -1
  178. package/dist/universal/.trae/skills/citable/schemas/audit-coverage.schema.json +118 -0
  179. package/dist/universal/.trae/skills/citable/schemas/browser-evidence-plan.schema.json +186 -3
  180. package/dist/universal/.trae/skills/citable/schemas/browser-network-events.schema.json +142 -0
  181. package/dist/universal/.trae/skills/citable/schemas/browser-runtime-events.schema.json +112 -0
  182. package/dist/universal/.trae/skills/citable/schemas/browser-state-observations.schema.json +97 -0
  183. package/dist/universal/.trae/skills/citable/schemas/collection-result.schema.json +35 -0
  184. package/dist/universal/.trae/skills/citable/schemas/config.schema.json +2 -0
  185. package/dist/universal/.trae/skills/citable/schemas/cro-report.schema.json +8 -1
  186. package/dist/universal/.trae/skills/citable/schemas/finding.schema.json +105 -2
  187. package/dist/universal/.trae/skills/citable/schemas/remediation-verification.schema.json +12 -1
  188. package/dist/universal/.trae/skills/citable/schemas/run.schema.json +15 -2
  189. package/dist/universal/.trae/skills/citable/schemas/search-report.schema.json +8 -1
  190. package/dist/universal/.trae/skills/citable/schemas/summary.schema.json +41 -0
  191. package/dist/universal/.trae-cn/skills/citable/SKILL.md +11 -2
  192. package/dist/universal/.trae-cn/skills/citable/VERSION +1 -1
  193. package/dist/universal/.trae-cn/skills/citable/manifest.json +19 -13
  194. package/dist/universal/.trae-cn/skills/citable/schemas/action-plan.schema.json +38 -1
  195. package/dist/universal/.trae-cn/skills/citable/schemas/audit-coverage.schema.json +118 -0
  196. package/dist/universal/.trae-cn/skills/citable/schemas/browser-evidence-plan.schema.json +186 -3
  197. package/dist/universal/.trae-cn/skills/citable/schemas/browser-network-events.schema.json +142 -0
  198. package/dist/universal/.trae-cn/skills/citable/schemas/browser-runtime-events.schema.json +112 -0
  199. package/dist/universal/.trae-cn/skills/citable/schemas/browser-state-observations.schema.json +97 -0
  200. package/dist/universal/.trae-cn/skills/citable/schemas/collection-result.schema.json +35 -0
  201. package/dist/universal/.trae-cn/skills/citable/schemas/config.schema.json +2 -0
  202. package/dist/universal/.trae-cn/skills/citable/schemas/cro-report.schema.json +8 -1
  203. package/dist/universal/.trae-cn/skills/citable/schemas/finding.schema.json +105 -2
  204. package/dist/universal/.trae-cn/skills/citable/schemas/remediation-verification.schema.json +12 -1
  205. package/dist/universal/.trae-cn/skills/citable/schemas/run.schema.json +15 -2
  206. package/dist/universal/.trae-cn/skills/citable/schemas/search-report.schema.json +8 -1
  207. package/dist/universal/.trae-cn/skills/citable/schemas/summary.schema.json +41 -0
  208. package/dist/universal/manifest.json +232 -160
  209. package/package.json +1 -1
  210. package/schemas/action-plan.schema.json +38 -1
  211. package/schemas/audit-coverage.schema.json +118 -0
  212. package/schemas/browser-evidence-plan.schema.json +186 -3
  213. package/schemas/browser-network-events.schema.json +142 -0
  214. package/schemas/browser-runtime-events.schema.json +112 -0
  215. package/schemas/browser-state-observations.schema.json +97 -0
  216. package/schemas/collection-result.schema.json +35 -0
  217. package/schemas/config.schema.json +2 -0
  218. package/schemas/cro-report.schema.json +8 -1
  219. package/schemas/finding.schema.json +105 -2
  220. package/schemas/remediation-verification.schema.json +12 -1
  221. package/schemas/run.schema.json +15 -2
  222. package/schemas/search-report.schema.json +8 -1
  223. package/schemas/summary.schema.json +41 -0
  224. package/skill/SKILL.md +11 -2
  225. package/src/acceptance/reproducibility.js +5 -25
  226. package/src/analysis/croRoadmap.js +3 -3
  227. package/src/analysis/strategicRoadmap.js +3 -3
  228. package/src/artifacts/interchange.js +2 -6
  229. package/src/cli/index.js +15 -3
  230. package/src/commands/actionPlan.js +46 -5
  231. package/src/commands/applyRemediation.js +5 -5
  232. package/src/commands/audit.js +75 -8
  233. package/src/commands/ciWorkflow.js +7 -2
  234. package/src/commands/compareSnapshots.js +155 -47
  235. package/src/commands/connect.js +24 -4
  236. package/src/commands/context.js +41 -2
  237. package/src/commands/croSuite.js +51 -13
  238. package/src/commands/delivery.js +7 -7
  239. package/src/commands/governance.js +9 -10
  240. package/src/commands/implementationKit.js +2 -1
  241. package/src/commands/init.js +8 -1
  242. package/src/commands/previewCro.js +1 -1
  243. package/src/commands/prioritize.js +3 -2
  244. package/src/commands/reviews.js +31 -5
  245. package/src/commands/roadmapCmd.js +7 -13
  246. package/src/commands/sweep.js +15 -2
  247. package/src/commands/verifyRemediation.js +94 -19
  248. package/src/connectors/collectionResult.js +80 -0
  249. package/src/connectors/ga4.js +46 -11
  250. package/src/connectors/gsc.js +41 -6
  251. package/src/connectors/index.js +1 -1
  252. package/src/connectors/webflow.js +140 -10
  253. package/src/connectors/wordpress.js +92 -8
  254. package/src/crawler/fetch.js +220 -24
  255. package/src/crawler/resourceValidity.js +119 -0
  256. package/src/crawler/sitemap.js +435 -16
  257. package/src/crawler/sitemapCollector.js +364 -0
  258. package/src/crawler/urlIdentity.js +97 -0
  259. package/src/detectors/agent.js +11 -0
  260. package/src/detectors/ans.js +16 -1
  261. package/src/detectors/arch.js +6 -0
  262. package/src/detectors/claim.js +9 -0
  263. package/src/detectors/crawl.js +7 -0
  264. package/src/detectors/cro.js +21 -0
  265. package/src/detectors/cwv.js +4 -0
  266. package/src/detectors/entity.js +7 -0
  267. package/src/detectors/evd.js +9 -0
  268. package/src/detectors/framework.js +28 -2
  269. package/src/detectors/geoReco.js +16 -0
  270. package/src/detectors/hreflang.js +3 -0
  271. package/src/detectors/lifeMeas.js +9 -0
  272. package/src/detectors/link.js +6 -1
  273. package/src/detectors/page.js +11 -0
  274. package/src/detectors/schemaData.js +16 -1
  275. package/src/detectors/tech.js +34 -3
  276. package/src/evidence/coverage.js +448 -0
  277. package/src/evidence/determination.js +157 -0
  278. package/src/evidence/downstream.js +110 -0
  279. package/src/evidence/hashes.js +109 -0
  280. package/src/evidence/run.js +68 -8
  281. package/src/extractor/page.js +29 -2
  282. package/src/extractor/site.js +298 -50
  283. package/src/observations/browser/correlation.js +83 -0
  284. package/src/observations/browser/networkCollector.js +413 -0
  285. package/src/observations/browser/runtimeEventCollector.js +337 -0
  286. package/src/observations/browser/sanitizer.js +322 -0
  287. package/src/observations/browser/stateCollector.js +386 -0
  288. package/src/observations/browserJourney.js +131 -3
  289. package/src/reporting/executiveCroReport.js +37 -14
  290. package/src/reporting/executiveExport.js +45 -16
  291. package/src/reporting/executiveSearchReport.js +57 -23
  292. package/src/reporting/report.js +56 -9
  293. package/src/shared/epistemicLanguage.js +86 -0
  294. package/src/shared/evidenceSourceResolver.js +48 -17
  295. package/src/shared/htmlEscape.js +8 -4
  296. package/src/shared/runPackageVerifier.js +99 -8
  297. package/src/shared/verifiedRunLoader.js +129 -0
  298. package/src/sow/generateSow.js +1 -1
@@ -0,0 +1,364 @@
1
+ import { gunzipSync } from 'node:zlib';
2
+ import { fetchUrl } from './fetch.js';
3
+ import { parseSitemap } from './sitemap.js';
4
+
5
+ const DEFAULT_MAX_DEPTH = 4;
6
+ const DEFAULT_MAX_DOCUMENTS = 1000;
7
+ const DEFAULT_MAX_UNCOMPRESSED_BYTES = 20 * 1024 * 1024;
8
+ const DEFAULT_MAX_TOTAL_UNCOMPRESSED_BYTES = 100 * 1024 * 1024;
9
+ const DEFAULT_MAX_DISCOVERED_URLS = 50_000;
10
+ const DEFAULT_MAX_QUEUED_DOCUMENTS = 1000;
11
+ const DEFAULT_MAX_RAW_ENTRIES = 100_000;
12
+ const DEFAULT_MAX_XML_TOKENS = 500_000;
13
+ const DEFAULT_SITEMAP_MAX_BYTES = 5 * 1024 * 1024;
14
+
15
+ /** Collect a same-origin sitemap graph with deterministic breadth-first traversal. */
16
+ export async function collectSitemapTopology(entryUrls, {
17
+ fetcher = fetchUrl,
18
+ maxDepth = DEFAULT_MAX_DEPTH,
19
+ maxDocuments = DEFAULT_MAX_DOCUMENTS,
20
+ maxUncompressedBytes = DEFAULT_MAX_UNCOMPRESSED_BYTES,
21
+ maxTotalUncompressedBytes = DEFAULT_MAX_TOTAL_UNCOMPRESSED_BYTES,
22
+ maxDiscoveredUrls = DEFAULT_MAX_DISCOVERED_URLS,
23
+ maxQueuedDocuments = DEFAULT_MAX_QUEUED_DOCUMENTS,
24
+ maxRawEntries = DEFAULT_MAX_RAW_ENTRIES,
25
+ maxXmlTokens = DEFAULT_MAX_XML_TOKENS,
26
+ sitemapMaxBytes = DEFAULT_SITEMAP_MAX_BYTES,
27
+ origin,
28
+ userAgent,
29
+ shouldStop = () => false,
30
+ } = {}) {
31
+ assertInteger(maxDepth, 'maxDepth', 0);
32
+ assertInteger(maxDocuments, 'maxDocuments', 1);
33
+ assertInteger(maxUncompressedBytes, 'maxUncompressedBytes', 1);
34
+ assertInteger(maxTotalUncompressedBytes, 'maxTotalUncompressedBytes', 1);
35
+ assertInteger(maxDiscoveredUrls, 'maxDiscoveredUrls', 1);
36
+ assertInteger(maxQueuedDocuments, 'maxQueuedDocuments', 1);
37
+ assertInteger(maxRawEntries, 'maxRawEntries', 1);
38
+ assertInteger(maxXmlTokens, 'maxXmlTokens', 1);
39
+ assertInteger(sitemapMaxBytes, 'sitemapMaxBytes', 1);
40
+ const entries = Array.isArray(entryUrls) ? entryUrls : [entryUrls];
41
+ const auditedOrigin = new URL(origin ?? entries.find(Boolean)).origin;
42
+ const queue = [];
43
+ const queued = new Set();
44
+ const documents = [];
45
+ const urls = [];
46
+ const seenUrls = new Set();
47
+ const exclusions = [];
48
+ const errors = [];
49
+ const limitations = [];
50
+ const stopReasons = [];
51
+ let totalUncompressedBytes = 0;
52
+ let queuedDocumentsPeak = 0;
53
+
54
+ const addStop = (reason, limitation) => {
55
+ if (!stopReasons.includes(reason)) stopReasons.push(reason);
56
+ if (limitation && !limitations.includes(limitation)) limitations.push(limitation);
57
+ };
58
+ const enqueue = (rawUrl, depth, parentUrl, kind) => {
59
+ let parsed;
60
+ try {
61
+ parsed = new URL(rawUrl, parentUrl ?? auditedOrigin);
62
+ parsed.hash = '';
63
+ } catch {
64
+ exclusions.push({ url: String(rawUrl ?? ''), kind, source_url: parentUrl, reason: 'malformed_url' });
65
+ errors.push({ url: String(rawUrl ?? ''), reason: 'malformed_url' });
66
+ return 'malformed_url';
67
+ }
68
+ if (parsed.origin !== auditedOrigin) {
69
+ exclusions.push({ url: parsed.href, kind, source_url: parentUrl, reason: 'out_of_origin' });
70
+ errors.push({ url: parsed.href, reason: 'out_of_origin' });
71
+ return 'out_of_origin';
72
+ }
73
+ if (queued.has(parsed.href)) return 'duplicate';
74
+ if (queue.length >= maxQueuedDocuments) {
75
+ addStop(
76
+ 'max_queued_documents_exceeded',
77
+ `Sitemap topology collection reached the ${maxQueuedDocuments}-document queued-frontier limit.`,
78
+ );
79
+ return 'max_queued_documents_exceeded';
80
+ }
81
+ queued.add(parsed.href);
82
+ queue.push({ url: parsed.href, depth, parentUrl });
83
+ queuedDocumentsPeak = Math.max(queuedDocumentsPeak, queue.length);
84
+ return null;
85
+ };
86
+
87
+ for (let index = 0; index < entries.length; index += 1) {
88
+ if (index >= maxRawEntries) {
89
+ addStop(
90
+ 'max_raw_entries_exceeded',
91
+ `Sitemap topology collection reached the ${maxRawEntries}-entry raw-input safety limit.`,
92
+ );
93
+ break;
94
+ }
95
+ enqueue(entries[index], 0, null, 'entry_sitemap');
96
+ }
97
+
98
+ collection: while (queue.length) {
99
+ if (shouldStop()) {
100
+ addStop('time_budget_exhausted', 'Sitemap topology collection stopped when the whole-run time budget expired.');
101
+ break;
102
+ }
103
+ if (documents.length >= maxDocuments) {
104
+ addStop('max_documents_exceeded', `Sitemap topology collection reached the ${maxDocuments}-document limit.`);
105
+ break;
106
+ }
107
+ const item = queue.shift();
108
+ if (item.depth > maxDepth) {
109
+ addStop('max_depth_exceeded', `Sitemap topology collection reached the maximum depth of ${maxDepth}.`);
110
+ continue;
111
+ }
112
+
113
+ const record = {
114
+ requested_url: item.url,
115
+ effective_url: item.url,
116
+ depth: item.depth,
117
+ parent_url: item.parentUrl,
118
+ http_status: null,
119
+ compression: 'none',
120
+ compression_transport_decoded: false,
121
+ parse_errors: [],
122
+ url_count: 0,
123
+ child_count: 0,
124
+ raw_entry_count: 0,
125
+ status: 'failed',
126
+ failure_reason: null,
127
+ failure_stage: null,
128
+ parsed: null,
129
+ };
130
+ documents.push(record);
131
+ try {
132
+ const response = await fetcher(item.url, {
133
+ userAgent,
134
+ maxBodyBytes: sitemapMaxBytes,
135
+ responseType: 'buffer',
136
+ });
137
+ record.effective_url = response.url ?? item.url;
138
+ record.http_status = response.status ?? null;
139
+ if (new URL(record.effective_url).origin !== auditedOrigin) {
140
+ record.failure_reason = 'effective_url_out_of_origin';
141
+ record.failure_stage = 'policy';
142
+ errors.push({ url: item.url, reason: record.failure_reason });
143
+ continue;
144
+ }
145
+ if (response.status !== 200) {
146
+ record.failure_reason = `http_status_${response.status}`;
147
+ record.failure_stage = 'transport';
148
+ errors.push({ url: item.url, reason: record.failure_reason });
149
+ continue;
150
+ }
151
+
152
+ record.compression = detectCompression(response.body, response.headers, record.effective_url);
153
+ const decoded = decodeBody(
154
+ response.body, response.headers, record.effective_url, maxUncompressedBytes, record.compression,
155
+ );
156
+ record.compression_transport_decoded = decoded.transportDecoded;
157
+ record.uncompressed_bytes = decoded.uncompressedBytes;
158
+ if (totalUncompressedBytes + decoded.uncompressedBytes > maxTotalUncompressedBytes) {
159
+ markTruncated(record, 'max_total_uncompressed_bytes_exceeded');
160
+ addStop(
161
+ 'max_total_uncompressed_bytes_exceeded',
162
+ `Sitemap topology collection exceeded the ${maxTotalUncompressedBytes}-byte cumulative uncompressed limit.`,
163
+ );
164
+ break collection;
165
+ }
166
+ totalUncompressedBytes += decoded.uncompressedBytes;
167
+ const parsed = parseSitemap(decoded.text, {
168
+ maxUrls: maxRawEntries,
169
+ maxChildren: maxRawEntries,
170
+ maxEntries: maxRawEntries,
171
+ maxTokens: maxXmlTokens,
172
+ });
173
+ record.parsed = parsed;
174
+ record.parse_errors = [...parsed.errors];
175
+ record.url_count = parsed.urlCount;
176
+ record.child_count = parsed.childCount;
177
+ record.raw_entry_count = parsed.rawEntryCount;
178
+ record.status = parsed.errors.length ? 'malformed' : 'fetched';
179
+ if (parsed.errors.length) {
180
+ record.failure_reason = 'sitemap_parse_error';
181
+ record.failure_stage = 'parse';
182
+ errors.push({ url: item.url, reason: record.failure_reason, details: [...parsed.errors] });
183
+ }
184
+ if (parsed.truncated) {
185
+ const [reason, limitation] = parsed.truncationReason === 'max_tokens_exceeded'
186
+ ? ['max_xml_tokens_exceeded', `Sitemap parsing reached the ${maxXmlTokens}-token structural limit.`]
187
+ : ['max_raw_entries_exceeded', `Sitemap parsing reached the ${maxRawEntries}-entry raw-input safety limit.`];
188
+ markTruncated(record, reason);
189
+ addStop(reason, limitation);
190
+ }
191
+
192
+ for (const child of parsed.rootValid ? parsed.children : []) {
193
+ const exclusionReason = enqueue(child.loc, item.depth + 1, record.effective_url, 'child_sitemap');
194
+ if (exclusionReason === 'malformed_url') markMalformedLoc(record, child.loc);
195
+ if (exclusionReason === 'max_queued_documents_exceeded') {
196
+ markTruncated(record, exclusionReason);
197
+ break;
198
+ }
199
+ }
200
+ for (const entry of parsed.rootValid ? parsed.urls : []) {
201
+ let page;
202
+ try {
203
+ page = new URL(entry.loc, record.effective_url);
204
+ page.hash = '';
205
+ } catch {
206
+ markMalformedLoc(record, entry.loc);
207
+ exclusions.push({ url: entry.loc, kind: 'page_url', source_url: record.effective_url, reason: 'malformed_url' });
208
+ errors.push({ url: entry.loc, reason: 'malformed_url' });
209
+ continue;
210
+ }
211
+ if (page.origin !== auditedOrigin) {
212
+ exclusions.push({ url: page.href, kind: 'page_url', source_url: record.effective_url, reason: 'out_of_origin' });
213
+ errors.push({ url: page.href, reason: 'out_of_origin' });
214
+ continue;
215
+ }
216
+ if (seenUrls.has(page.href)) continue;
217
+ if (urls.length >= maxDiscoveredUrls) {
218
+ markTruncated(record, 'max_discovered_urls_exceeded');
219
+ addStop(
220
+ 'max_discovered_urls_exceeded',
221
+ `Sitemap topology collection reached the ${maxDiscoveredUrls}-URL discovery limit.`,
222
+ );
223
+ break;
224
+ }
225
+ seenUrls.add(page.href);
226
+ urls.push({ url: page.href, sitemap_url: record.effective_url, requested_sitemap_url: item.url, sitemap_depth: item.depth });
227
+ }
228
+ if (stopReasons.includes('max_discovered_urls_exceeded')) break collection;
229
+ } catch (error) {
230
+ record.failure_reason = classifyFailure(error);
231
+ record.failure_stage = failureStage(record.failure_reason);
232
+ errors.push({ url: item.url, reason: record.failure_reason, message: error.message });
233
+ }
234
+ }
235
+
236
+ if (shouldStop()) {
237
+ addStop('time_budget_exhausted', 'Sitemap topology collection stopped when the whole-run time budget expired.');
238
+ }
239
+
240
+ const truncated = stopReasons.length > 0;
241
+ const indeterminate = documents.some((document) => ['failed', 'malformed'].includes(document.status))
242
+ || errors.some((error) => ['malformed_url', 'out_of_origin'].includes(error.reason));
243
+ return {
244
+ documents,
245
+ urls,
246
+ status: truncated ? 'truncated' : indeterminate ? 'indeterminate' : 'complete',
247
+ limitations,
248
+ errors,
249
+ exclusions,
250
+ stop_reasons: stopReasons,
251
+ bounds: {
252
+ max_depth: maxDepth,
253
+ max_documents: maxDocuments,
254
+ max_uncompressed_bytes: maxUncompressedBytes,
255
+ max_total_uncompressed_bytes: maxTotalUncompressedBytes,
256
+ max_discovered_urls: maxDiscoveredUrls,
257
+ max_queued_documents: maxQueuedDocuments,
258
+ max_raw_entries: maxRawEntries,
259
+ max_xml_tokens: maxXmlTokens,
260
+ sitemap_max_bytes: sitemapMaxBytes,
261
+ },
262
+ totals: {
263
+ documents: documents.length,
264
+ urls: urls.length,
265
+ uncompressed_bytes: totalUncompressedBytes,
266
+ queued_documents_peak: queuedDocumentsPeak,
267
+ },
268
+ };
269
+ }
270
+
271
+ function detectCompression(body, headers = {}, url) {
272
+ const bytes = Buffer.isBuffer(body)
273
+ ? body
274
+ : body instanceof Uint8Array
275
+ ? Buffer.from(body.buffer, body.byteOffset, body.byteLength)
276
+ : Buffer.from(String(body ?? ''), 'utf8');
277
+ const contentEncoding = String(headerValue(headers, 'content-encoding') ?? '').toLowerCase();
278
+ const contentType = String(headerValue(headers, 'content-type') ?? '').toLowerCase();
279
+ const magicGzip = bytes.length >= 2 && bytes[0] === 0x1f && bytes[1] === 0x8b;
280
+ const hintedGzip = magicGzip || /(?:^|\W)gzip(?:\W|$)/.test(contentEncoding)
281
+ || /(?:application|text)\/(?:x-)?gzip/.test(contentType) || new URL(url).pathname.toLowerCase().endsWith('.gz');
282
+ return hintedGzip ? 'gzip' : 'none';
283
+ }
284
+
285
+ function decodeBody(body, headers, url, maxUncompressedBytes, compression = detectCompression(body, headers, url)) {
286
+ const bytes = Buffer.isBuffer(body)
287
+ ? body
288
+ : body instanceof Uint8Array
289
+ ? Buffer.from(body.buffer, body.byteOffset, body.byteLength)
290
+ : Buffer.from(String(body ?? ''), 'utf8');
291
+ const magicGzip = bytes.length >= 2 && bytes[0] === 0x1f && bytes[1] === 0x8b;
292
+ let decoded = bytes;
293
+ let transportDecoded = false;
294
+ if (compression === 'gzip') {
295
+ if (magicGzip) {
296
+ decoded = gunzipSync(bytes, { maxOutputLength: maxUncompressedBytes });
297
+ } else if (looksLikeDecodedXml(bytes)) {
298
+ transportDecoded = true;
299
+ } else {
300
+ const error = new Error('gzip-indicated sitemap body is neither gzip bytes nor transport-decoded XML');
301
+ error.code = 'SITEMAP_DECOMPRESSION_FAILED';
302
+ throw error;
303
+ }
304
+ }
305
+ if (decoded.byteLength > maxUncompressedBytes) {
306
+ const error = new Error(`sitemap uncompressed body exceeds ${maxUncompressedBytes} bytes`);
307
+ error.code = 'SITEMAP_UNCOMPRESSED_LIMIT';
308
+ throw error;
309
+ }
310
+ return { text: decoded.toString('utf8'), compression, transportDecoded, uncompressedBytes: decoded.byteLength };
311
+ }
312
+
313
+ function looksLikeDecodedXml(bytes) {
314
+ return /^\s*</u.test(bytes.toString('utf8').replace(/^\uFEFF/u, ''));
315
+ }
316
+
317
+ function headerValue(headers, name) {
318
+ if (headers && typeof headers.get === 'function') return headers.get(name);
319
+ const key = Object.keys(headers ?? {}).find((candidate) => candidate.toLowerCase() === name);
320
+ return key ? headers[key] : undefined;
321
+ }
322
+
323
+ function classifyFailure(error) {
324
+ if (error?.code === 'FETCH_BODY_LIMIT' || /^response body exceeds \d+ bytes$/i.test(error?.message ?? '')) {
325
+ return 'sitemap_transport_bytes_exceeded';
326
+ }
327
+ if (error?.code === 'SITEMAP_UNCOMPRESSED_LIMIT' || error?.code === 'ERR_BUFFER_TOO_LARGE'
328
+ || /maxoutputlength|larger than/i.test(error?.message ?? '')) {
329
+ return 'max_uncompressed_bytes_exceeded';
330
+ }
331
+ if (error?.code === 'SITEMAP_DECOMPRESSION_FAILED'
332
+ || /gzip|incorrect header|invalid distance|unexpected end/i.test(error?.message ?? '')) return 'decompression_failed';
333
+ return 'fetch_failed';
334
+ }
335
+
336
+ function markMalformedLoc(record, loc) {
337
+ const message = `entry has malformed <loc>: ${loc}`;
338
+ if (!record.parse_errors.includes(message)) record.parse_errors.push(message);
339
+ record.status = 'malformed';
340
+ record.failure_reason = 'sitemap_parse_error';
341
+ record.failure_stage = 'parse';
342
+ }
343
+
344
+ function markTruncated(record, reason) {
345
+ record.status = 'truncated';
346
+ record.failure_reason = reason;
347
+ record.failure_stage = 'collection';
348
+ }
349
+
350
+ function failureStage(reason) {
351
+ if (reason === 'sitemap_transport_bytes_exceeded' || reason === 'fetch_failed' || reason.startsWith('http_status_')) {
352
+ return 'transport';
353
+ }
354
+ if (reason === 'max_uncompressed_bytes_exceeded' || reason === 'decompression_failed') return 'decompression';
355
+ if (reason === 'sitemap_parse_error') return 'parse';
356
+ if (reason.includes('out_of_origin')) return 'policy';
357
+ return 'collection';
358
+ }
359
+
360
+ function assertInteger(value, name, minimum) {
361
+ if (!Number.isInteger(value) || value < minimum) {
362
+ throw new TypeError(`${name} must be an integer greater than or equal to ${minimum}`);
363
+ }
364
+ }
@@ -0,0 +1,97 @@
1
+ import { sha256 } from '../shared/io.js';
2
+
3
+ export const URL_IDENTITY_NORMALIZATION_VERSION = 'url-identity-v1';
4
+
5
+ /**
6
+ * URL identity v1 deliberately performs only two transformations: remove the
7
+ * fragment and remove an explicit default HTTP(S) port. Query ordering,
8
+ * trailing slashes, casing, percent encoding, and host spelling are evidence
9
+ * and are therefore preserved.
10
+ */
11
+ export function normalizeUrlIdentity(url) {
12
+ if (typeof url !== 'string' || !url) throw new TypeError('URL identity requires a non-empty URL string');
13
+ const fragmentAt = url.indexOf('#');
14
+ const withoutFragment = fragmentAt === -1 ? url : url.slice(0, fragmentAt);
15
+ const match = withoutFragment.match(/^([A-Za-z][A-Za-z\d+.-]*):\/\/([^/?#]*)([\s\S]*)$/);
16
+ if (!match) throw new TypeError(`URL identity requires an absolute URL: ${url}`);
17
+ const [, rawScheme, rawAuthority, suffix] = match;
18
+ const scheme = rawScheme.toLowerCase();
19
+ if (scheme !== 'http' && scheme !== 'https') throw new TypeError(`unsupported URL identity protocol: ${rawScheme}:`);
20
+ if (rawAuthority.includes('@')) throw new TypeError('URL credentials are not permitted');
21
+
22
+ // Parse for validity and policy comparisons, but never serialize with URL:
23
+ // WHATWG serialization would lowercase evidence-bearing scheme/host text.
24
+ const parsed = new URL(withoutFragment);
25
+ if (parsed.username || parsed.password) throw new TypeError('URL credentials are not permitted');
26
+ let authority = rawAuthority;
27
+ const defaultPort = scheme === 'https' ? '443' : '80';
28
+ const portPattern = new RegExp(`:${defaultPort}$`);
29
+ if (portPattern.test(authority)) authority = authority.replace(portPattern, '');
30
+ return `${rawScheme}://${authority}${suffix}`;
31
+ }
32
+
33
+ function assertSameOrigin(candidate, allowedOrigin, label) {
34
+ if (new URL(candidate).origin !== allowedOrigin) {
35
+ throw new Error(`${label} origin differs from requested URL origin`);
36
+ }
37
+ }
38
+
39
+ function identityPart(url, baseUrl) {
40
+ if (url == null || url === '') return null;
41
+ const raw = String(url);
42
+ const absolute = baseUrl == null || /^[A-Za-z][A-Za-z\d+.-]*:\/\//.test(raw)
43
+ ? raw
44
+ : new URL(raw, baseUrl).href;
45
+ return { url: absolute, normalized_url: normalizeUrlIdentity(absolute) };
46
+ }
47
+
48
+ function optionalIdentityPart(url, baseUrl) {
49
+ if (url == null || url === '') return null;
50
+ try {
51
+ return identityPart(url, baseUrl);
52
+ } catch {
53
+ // Preserve an invalid declaration for downstream detectors instead of
54
+ // turning URL metadata enrichment into a page extraction failure.
55
+ return { url: String(url), normalized_url: null };
56
+ }
57
+ }
58
+
59
+ /** Build a provenance-preserving identity without treating canonical as fetch identity. */
60
+ export function createUrlIdentity({
61
+ requestedUrl,
62
+ effectiveUrl,
63
+ redirectChain = [],
64
+ declaredCanonicalUrl = null,
65
+ } = {}) {
66
+ const requested = identityPart(requestedUrl);
67
+ const effective = identityPart(effectiveUrl);
68
+ if (!requested || !effective) throw new TypeError('requestedUrl and effectiveUrl are required');
69
+ const allowedOrigin = new URL(requested.url).origin;
70
+ assertSameOrigin(effective.url, allowedOrigin, 'effective URL');
71
+
72
+ const redirect = (redirectChain || []).map((step) => {
73
+ const source = identityPart(step.url);
74
+ if (!source) throw new TypeError('redirect URL is required');
75
+ assertSameOrigin(source.url, allowedOrigin, 'redirect URL');
76
+ const destination = step.location == null ? null : identityPart(step.location, source.url);
77
+ if (destination) assertSameOrigin(destination.url, allowedOrigin, 'redirect destination');
78
+ return {
79
+ url: source.url,
80
+ normalized_url: source.normalized_url,
81
+ status: step.status,
82
+ location: step.location ?? null,
83
+ destination_url: destination?.url ?? null,
84
+ normalized_destination_url: destination?.normalized_url ?? null,
85
+ };
86
+ });
87
+ const canonical = optionalIdentityPart(declaredCanonicalUrl, effective.url);
88
+
89
+ return {
90
+ normalization_version: URL_IDENTITY_NORMALIZATION_VERSION,
91
+ requested,
92
+ effective,
93
+ redirect,
94
+ canonical,
95
+ resource_id: `RESOURCE-${sha256(effective.normalized_url).slice(0, 24).toUpperCase()}`,
96
+ };
97
+ }
@@ -50,6 +50,7 @@ function siteUrl(ctx) {
50
50
  // ---------------------------------------------------------------------------
51
51
 
52
52
  export const AGENT_001 = defineDetector({
53
+ coverage_requirement: 'exhaustive_requested_scope',
53
54
  id: 'AGENT-001',
54
55
  name: 'AI bot rules absent from robots.txt',
55
56
  namespace: 'AGENT',
@@ -94,6 +95,7 @@ export const AGENT_001 = defineDetector({
94
95
  // ---------------------------------------------------------------------------
95
96
 
96
97
  export const AGENT_002 = defineDetector({
98
+ coverage_requirement: 'evaluated_subset',
97
99
  id: 'AGENT-002',
98
100
  name: 'Link response headers absent',
99
101
  namespace: 'AGENT',
@@ -131,6 +133,7 @@ export const AGENT_002 = defineDetector({
131
133
  // ---------------------------------------------------------------------------
132
134
 
133
135
  export const AGENT_003 = defineDetector({
136
+ coverage_requirement: 'exhaustive_requested_scope',
134
137
  id: 'AGENT-003',
135
138
  name: 'llms.txt missing',
136
139
  namespace: 'AGENT',
@@ -169,6 +172,7 @@ export const AGENT_003 = defineDetector({
169
172
  // ---------------------------------------------------------------------------
170
173
 
171
174
  export const AGENT_004 = defineDetector({
175
+ coverage_requirement: 'exhaustive_requested_scope',
172
176
  id: 'AGENT-004',
173
177
  name: 'MCP Server Card not discoverable',
174
178
  namespace: 'AGENT',
@@ -215,6 +219,7 @@ export const AGENT_004 = defineDetector({
215
219
  // ---------------------------------------------------------------------------
216
220
 
217
221
  export const AGENT_005 = defineDetector({
222
+ coverage_requirement: 'exhaustive_requested_scope',
218
223
  id: 'AGENT-005',
219
224
  name: 'A2A Agent Card not discoverable',
220
225
  namespace: 'AGENT',
@@ -258,6 +263,7 @@ export const AGENT_005 = defineDetector({
258
263
  // ---------------------------------------------------------------------------
259
264
 
260
265
  export const AGENT_006 = defineDetector({
266
+ coverage_requirement: 'evaluated_subset',
261
267
  id: 'AGENT-006',
262
268
  name: 'Markdown content negotiation not supported',
263
269
  namespace: 'AGENT',
@@ -302,6 +308,7 @@ export const AGENT_006 = defineDetector({
302
308
  // ---------------------------------------------------------------------------
303
309
 
304
310
  export const AGENT_007 = defineDetector({
311
+ coverage_requirement: 'evaluated_subset',
305
312
  id: 'AGENT-007',
306
313
  name: 'Web Bot Auth not declared',
307
314
  namespace: 'AGENT',
@@ -345,6 +352,7 @@ export const AGENT_007 = defineDetector({
345
352
  // ---------------------------------------------------------------------------
346
353
 
347
354
  export const AGENT_008 = defineDetector({
355
+ coverage_requirement: 'evaluated_subset',
348
356
  id: 'AGENT-008',
349
357
  name: 'Content-Signals header absent',
350
358
  namespace: 'AGENT',
@@ -386,6 +394,7 @@ export const AGENT_008 = defineDetector({
386
394
  // ---------------------------------------------------------------------------
387
395
 
388
396
  export const AGENT_009 = defineDetector({
397
+ coverage_requirement: 'evaluated_subset',
389
398
  id: 'AGENT-009',
390
399
  name: 'auth.md not present',
391
400
  namespace: 'AGENT',
@@ -431,6 +440,7 @@ export const AGENT_009 = defineDetector({
431
440
  // ---------------------------------------------------------------------------
432
441
 
433
442
  export const AGENT_010 = defineDetector({
443
+ coverage_requirement: 'evaluated_subset',
434
444
  id: 'AGENT-010',
435
445
  name: 'Agentic commerce protocols not declared',
436
446
  namespace: 'AGENT',
@@ -488,6 +498,7 @@ export const AGENT_010 = defineDetector({
488
498
  // ---------------------------------------------------------------------------
489
499
 
490
500
  export const AGENT_011 = defineDetector({
501
+ coverage_requirement: 'evaluated_subset',
491
502
  id: 'AGENT-011',
492
503
  name: 'llms.txt structure or link integrity broken',
493
504
  namespace: 'AGENT',
@@ -6,6 +6,7 @@ const D = [];
6
6
  const PREAMBLE_RX = /\b(in today'?s (rapidly |ever[- ])?(evolving|changing)|in the (modern|current|digital) (era|age|landscape|world)|now more than ever|as (technology|ai) continues to (evolve|advance)|in an increasingly)\b/i;
7
7
 
8
8
  D.push(defineDetector({
9
+ coverage_requirement: 'evaluated_subset',
9
10
  id: 'ANS-001', name: 'Generic preamble before answer', namespace: 'ANS',
10
11
  description: 'The opening paragraph uses generic scene-setting boilerplate instead of answering the page’s question.',
11
12
  discipline: ['aeo', 'geo'], severity: 'medium', deterministic: false, requires: ['site'],
@@ -26,6 +27,7 @@ D.push(defineDetector({
26
27
  }));
27
28
 
28
29
  D.push(defineDetector({
30
+ coverage_requirement: 'evaluated_subset',
29
31
  id: 'ANS-002', name: 'Question heading without direct answer', namespace: 'ANS',
30
32
  description: 'A heading phrased as a question is not followed by prose before the next heading.',
31
33
  discipline: ['aeo'], severity: 'medium', deterministic: true, requires: ['site'],
@@ -53,6 +55,7 @@ D.push(defineDetector({
53
55
  }));
54
56
 
55
57
  D.push(defineDetector({
58
+ coverage_requirement: 'evaluated_subset',
56
59
  id: 'ANS-003', name: 'Circular definition', namespace: 'ANS',
57
60
  description: 'A definitional sentence defines a term using the term itself as the definition head.',
58
61
  discipline: ['aeo', 'geo'], severity: 'medium', deterministic: false, requires: ['site'],
@@ -85,6 +88,7 @@ D.push(defineDetector({
85
88
  }));
86
89
 
87
90
  D.push(defineDetector({
91
+ coverage_requirement: 'evaluated_subset',
88
92
  id: 'ANS-004', name: 'Deictic dependency in answer text', namespace: 'ANS',
89
93
  description: 'Answer-bearing prose depends on preceding visual context ("as shown above", "in the diagram below"), which breaks when extracted.',
90
94
  discipline: ['aeo'], severity: 'low', deterministic: true, requires: ['site'],
@@ -108,6 +112,7 @@ D.push(defineDetector({
108
112
  }));
109
113
 
110
114
  D.push(defineDetector({
115
+ coverage_requirement: 'evaluated_subset',
111
116
  id: 'ANS-005', name: 'Relative quantity without baseline or timeframe', namespace: 'ANS',
112
117
  description: 'Text asserts a relative numeric improvement ("3x faster", "50% reduction") without a nearby baseline, unit, or timeframe.',
113
118
  discipline: ['aeo', 'geo'], severity: 'medium', deterministic: false, requires: ['site'],
@@ -138,6 +143,7 @@ D.push(defineDetector({
138
143
  }));
139
144
 
140
145
  D.push(defineDetector({
146
+ coverage_requirement: 'evaluated_subset',
141
147
  id: 'ANS-006', name: 'Procedure content without ordered steps', namespace: 'ANS',
142
148
  description: 'A page registered as implementation/how-to content contains no ordered list or numbered steps.',
143
149
  discipline: ['aeo'], severity: 'low', deterministic: true, requires: ['site', 'registries'],
@@ -164,6 +170,7 @@ D.push(defineDetector({
164
170
  }));
165
171
 
166
172
  D.push(defineDetector({
173
+ coverage_requirement: 'evaluated_subset',
167
174
  id: 'ANS-007', name: 'Comparison without explicit basis', namespace: 'ANS',
168
175
  description: 'A comparison page has neither a comparison table nor prose stating the comparison criteria.',
169
176
  discipline: ['aeo', 'geo'], severity: 'medium', deterministic: true, requires: ['site', 'registries'],
@@ -190,6 +197,7 @@ D.push(defineDetector({
190
197
  }));
191
198
 
192
199
  D.push(defineDetector({
200
+ coverage_requirement: 'evaluated_subset',
193
201
  id: 'ANS-008', name: 'Missing scope or limitations on answer page', namespace: 'ANS',
194
202
  description: 'A definition/comparison/recommendation page never states scope boundaries, exclusions, or limitations.',
195
203
  discipline: ['aeo', 'geo'], severity: 'medium', deterministic: false, requires: ['site', 'registries'],
@@ -218,6 +226,7 @@ D.push(defineDetector({
218
226
  }));
219
227
 
220
228
  D.push(defineDetector({
229
+ coverage_requirement: 'evaluated_subset',
221
230
  id: 'ANS-009', name: 'Answer-target page has no prompt coverage', namespace: 'ANS',
222
231
  description: 'An answer-bearing page is intended for discovery but is not mapped to any prompt in the governed prompt corpus.',
223
232
  discipline: ['aeo', 'geo'], severity: 'medium', deterministic: true, requires: ['site', 'registries'],
@@ -240,6 +249,7 @@ D.push(defineDetector({
240
249
  }));
241
250
 
242
251
  D.push(defineDetector({
252
+ coverage_requirement: 'evaluated_subset',
243
253
  id: 'ANS-010', name: 'Published claims lack page evidence mapping', namespace: 'ANS',
244
254
  description: 'A page publishes governed claims but records no page-level evidence references, preventing evidence-adjacency review.',
245
255
  discipline: ['aeo', 'geo'], severity: 'high', deterministic: true, requires: ['site', 'registries'],
@@ -261,6 +271,7 @@ D.push(defineDetector({
261
271
  }));
262
272
 
263
273
  D.push(defineDetector({
274
+ coverage_requirement: 'evaluated_subset',
264
275
  id: 'ANS-011', name: 'Over-diluted answer passage (missing direct answer lead)', namespace: 'ANS',
265
276
  description: 'A section targeting an informational query or question heading lacks a concise, direct answer passage in its opening sentences, burying key factual answers behind excessive preamble or rambling prose.',
266
277
  discipline: ['aeo', 'geo'], severity: 'medium', deterministic: false, requires: ['site'],
@@ -320,6 +331,7 @@ D.push(defineDetector({
320
331
  }));
321
332
 
322
333
  D.push(defineDetector({
334
+ coverage_requirement: 'evaluated_subset',
323
335
  id: 'ANS-012', name: 'Ungrounded quantitative metric in answer passage', namespace: 'ANS',
324
336
  description: 'An answer-bearing passage asserts high-stakes quantitative metrics, percentages, or multiples without adjacent evidence citation, benchmark reference, or registered evidence link.',
325
337
  discipline: ['aeo', 'geo'], severity: 'medium', deterministic: false, requires: ['site'],
@@ -359,6 +371,7 @@ D.push(defineDetector({
359
371
  }));
360
372
 
361
373
  D.push(defineDetector({
374
+ coverage_requirement: 'evaluated_subset',
362
375
  id: 'ANS-013', name: 'Comparative or procedural section lacking structured table or ordered sequence', namespace: 'ANS',
363
376
  description: 'A section covering comparative evaluation (vs, alternatives, comparison) or multi-step execution (how to, steps) lacks structured <table>, <ol>, or <ul> markup, diminishing answer engine extraction fidelity.',
364
377
  discipline: ['aeo', 'seo'], severity: 'medium', deterministic: true, requires: ['site'],
@@ -420,6 +433,7 @@ D.push(defineDetector({
420
433
  }));
421
434
 
422
435
  D.push(defineDetector({
436
+ coverage_requirement: 'evaluated_subset',
423
437
  id: 'ANS-014', name: 'Low information-gain fluff ratio in answer prose', namespace: 'ANS',
424
438
  description: 'An answer passage contains excessive rhetorical filler, clichés, or conversational padding, reducing factual density for generative engine extraction.',
425
439
  discipline: ['aeo', 'geo'], severity: 'low', deterministic: true, requires: ['site'],
@@ -467,6 +481,7 @@ D.push(defineDetector({
467
481
  }));
468
482
 
469
483
  D.push(defineDetector({
484
+ coverage_requirement: 'evaluated_subset',
470
485
  id: 'ANS-015', name: 'Definitional page lacks direct copular definition', namespace: 'ANS',
471
486
  description: 'A page with definitional intent (page_type=definition, /definition/, /glossary/, or "What is" heading) does not provide a direct copular definition ("X is a Y that Z") in the opening paragraph.',
472
487
  discipline: ['aeo', 'geo'], severity: 'medium', deterministic: true, requires: ['site'],
@@ -506,6 +521,7 @@ D.push(defineDetector({
506
521
  }));
507
522
 
508
523
  D.push(defineDetector({
524
+ coverage_requirement: 'evaluated_subset',
509
525
  id: 'ANS-016', name: 'Comprehensive long-form guide lacks executive summary or key takeaways', namespace: 'ANS',
510
526
  description: 'An in-depth article or guide exceeding 1,200 words does not provide an executive summary, key takeaways, or TL;DR block, increasing RAG chunk fragmentation and extraction ambiguity.',
511
527
  discipline: ['aeo', 'geo'], severity: 'low', deterministic: true, requires: ['site'],
@@ -541,4 +557,3 @@ D.push(defineDetector({
541
557
  }));
542
558
 
543
559
  export default D;
544
-