@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
@@ -3,7 +3,106 @@ import path from 'node:path';
3
3
  import { extractPage } from './page.js';
4
4
  import { parseRobots } from '../crawler/robots.js';
5
5
  import { parseSitemap } from '../crawler/sitemap.js';
6
+ import { collectSitemapTopology } from '../crawler/sitemapCollector.js';
6
7
  import { fetchUrl } from '../crawler/fetch.js';
8
+ import { classifyResource } from '../crawler/resourceValidity.js';
9
+ import { createUrlIdentity } from '../crawler/urlIdentity.js';
10
+ import { createCoverageLedger } from '../evidence/coverage.js';
11
+ import { hashPageArtifact } from '../evidence/hashes.js';
12
+
13
+ const MAX_PAGE_FETCH_ATTEMPTS = 100;
14
+ const SAFE_FETCH_ERROR_CODES = new Set([
15
+ 'FETCH_ABORTED', 'FETCH_BODY_LIMIT', 'FETCH_ERROR', 'FETCH_REDIRECT_INVALID',
16
+ 'FETCH_REDIRECT_LIMIT', 'FETCH_REDIRECT_ORIGIN', 'FETCH_TIMEOUT',
17
+ 'EAI_AGAIN', 'ECONNREFUSED', 'ECONNRESET', 'EHOSTUNREACH', 'ENETUNREACH',
18
+ 'ENOTFOUND', 'EPIPE', 'ETIMEDOUT', 'UND_ERR_CONNECT_TIMEOUT',
19
+ ]);
20
+
21
+ function sanitizeEvidenceUrl(rawUrl) {
22
+ try {
23
+ const url = new URL(String(rawUrl));
24
+ url.hash = '';
25
+ if (url.search) url.search = '?[REDACTED]';
26
+ return url.href;
27
+ } catch {
28
+ return '[invalid-url]';
29
+ }
30
+ }
31
+
32
+ function safeErrorCode(value, fallback = 'FETCH_ERROR') {
33
+ return typeof value === 'string' && SAFE_FETCH_ERROR_CODES.has(value) ? value : fallback;
34
+ }
35
+
36
+ function finiteNonNegative(value) {
37
+ return Number.isFinite(value) && value >= 0 ? value : null;
38
+ }
39
+
40
+ function sanitizeFetchAttempts(attempts) {
41
+ if (!Array.isArray(attempts)) return { attempts: [], truncated: false };
42
+ const bounded = attempts.slice(0, MAX_PAGE_FETCH_ATTEMPTS).map((rawAttempt, index) => {
43
+ const attempt = rawAttempt && typeof rawAttempt === 'object' ? rawAttempt : {};
44
+ const outcome = attempt.outcome === 'response' ? 'response' : 'error';
45
+ return {
46
+ attempt: Number.isInteger(attempt.attempt) && attempt.attempt > 0 ? attempt.attempt : index + 1,
47
+ url: sanitizeEvidenceUrl(attempt.url),
48
+ startedAtMs: finiteNonNegative(attempt.startedAtMs),
49
+ endedAtMs: finiteNonNegative(attempt.endedAtMs),
50
+ elapsedMs: finiteNonNegative(attempt.elapsedMs),
51
+ outcome,
52
+ status: Number.isInteger(attempt.status) && attempt.status >= 100 && attempt.status <= 599
53
+ ? attempt.status
54
+ : null,
55
+ errorCode: outcome === 'error' ? safeErrorCode(attempt.errorCode) : null,
56
+ retryDecision: ['retry', 'stop', 'redirect', 'complete'].includes(attempt.retryDecision)
57
+ ? attempt.retryDecision
58
+ : 'stop',
59
+ };
60
+ });
61
+ return { attempts: bounded, truncated: attempts.length > MAX_PAGE_FETCH_ATTEMPTS };
62
+ }
63
+
64
+ function sanitizedFetchError(rawUrl, error) {
65
+ const attempts = sanitizeFetchAttempts(error?.attempts).attempts;
66
+ const reason = safeErrorCode(error?.code, attempts.at(-1)?.errorCode ?? 'FETCH_ERROR');
67
+ return `${sanitizeEvidenceUrl(rawUrl)}: ${reason}`;
68
+ }
69
+
70
+ function sanitizedSitemapError(error) {
71
+ const reason = typeof error?.reason === 'string' && /^[a-z0-9_]{1,80}$/.test(error.reason)
72
+ ? error.reason
73
+ : 'sitemap_error';
74
+ return `${sanitizeEvidenceUrl(error?.url)}: ${reason}`;
75
+ }
76
+
77
+ function attachResourceMetadata(page, {
78
+ requestedUrl,
79
+ bodyComplete = true,
80
+ fetchAttempts = [],
81
+ } = {}) {
82
+ const effectiveUrl = page.url;
83
+ const declaredCanonicalUrl = page.canonicals[0] ?? null;
84
+ page.requestedUrl = requestedUrl ?? effectiveUrl;
85
+ page.bodyComplete = bodyComplete === true;
86
+ const sanitizedAttempts = sanitizeFetchAttempts(fetchAttempts);
87
+ page.fetchAttempts = sanitizedAttempts.attempts;
88
+ page.fetchAttemptsTruncated = sanitizedAttempts.truncated;
89
+ page.resourceValidity = classifyResource({
90
+ status: page.status,
91
+ headers: page.headers,
92
+ body: page.rawHtml,
93
+ bodyComplete: page.bodyComplete,
94
+ requestedUrl: page.requestedUrl,
95
+ effectiveUrl,
96
+ });
97
+ page.urlIdentity = createUrlIdentity({
98
+ requestedUrl: page.requestedUrl,
99
+ effectiveUrl,
100
+ redirectChain: page.redirectChain,
101
+ declaredCanonicalUrl,
102
+ });
103
+ page.artifact_hash = hashPageArtifact(page);
104
+ return page;
105
+ }
7
106
 
8
107
  /**
9
108
  * Build a SiteModel from a directory of built/static HTML output.
@@ -27,16 +126,17 @@ export function buildSiteFromDir(dir, { baseUrl = 'https://example.test' } = {})
27
126
  let urlPath = '/' + rel;
28
127
  if (urlPath.endsWith('/index.html')) urlPath = urlPath.slice(0, -'index.html'.length);
29
128
  const t = transport['/' + rel] || transport[urlPath] || {};
30
- pages.push(
31
- extractPage({
32
- url: new URL(urlPath, baseUrl).href,
33
- html: fs.readFileSync(p, 'utf8'),
34
- status: t.status ?? 200,
35
- headers: { 'content-type': 'text/html', ...(t.headers || {}) },
36
- sourceFile: p,
37
- redirectChain: [],
38
- })
39
- );
129
+ const responseBody = fs.readFileSync(p);
130
+ const page = extractPage({
131
+ url: new URL(urlPath, baseUrl).href,
132
+ html: responseBody.toString('utf8'),
133
+ responseBody,
134
+ status: t.status ?? 200,
135
+ headers: { 'content-type': 'text/html', ...(t.headers || {}) },
136
+ sourceFile: p,
137
+ redirectChain: [],
138
+ });
139
+ pages.push(attachResourceMetadata(page, { requestedUrl: page.url }));
40
140
  }
41
141
  }
42
142
  };
@@ -62,68 +162,216 @@ export function buildSiteFromDir(dir, { baseUrl = 'https://example.test' } = {})
62
162
 
63
163
  /** Build a SiteModel by fetching a deployed URL set (target URL + same-origin discovery, bounded). */
64
164
  export async function buildSiteFromUrl(startUrl, {
65
- maxPages = 50, userAgent, pageMaxBytes = 5 * 1024 * 1024,
165
+ maxPages = 500, timeBudgetSeconds = 1800, userAgent, pageMaxBytes = 5 * 1024 * 1024,
166
+ // Collection is intentionally sequential. Reject unsupported values rather
167
+ // than silently changing reproducibility or budget semantics.
168
+ concurrency = 1,
66
169
  robotsMaxBytes = 512 * 1024, sitemapMaxBytes = 5 * 1024 * 1024,
170
+ sitemapMaxDepth = 4, sitemapMaxDocuments = 1000,
171
+ sitemapMaxUncompressedBytes = 20 * 1024 * 1024,
172
+ sitemapMaxTotalUncompressedBytes = 100 * 1024 * 1024,
173
+ sitemapMaxDiscoveredUrls = 50_000, sitemapMaxQueuedDocuments = 1000,
174
+ sitemapMaxRawEntries = 100_000, sitemapMaxXmlTokens = 500_000,
67
175
  fetcher = fetchUrl,
176
+ now = () => performance.now(),
68
177
  } = {}) {
178
+ if (concurrency !== 1) {
179
+ throw new RangeError(`URL collection concurrency is fixed at 1; received ${String(concurrency)}`);
180
+ }
69
181
  const origin = new URL(startUrl).origin;
70
- const seen = new Set();
71
- const queue = [startUrl];
182
+ const coverageLedger = createCoverageLedger({ startUrl, maxPages, timeBudgetSeconds });
183
+ const startedAt = now();
184
+ const timeExpired = () => now() - startedAt >= timeBudgetSeconds * 1000;
72
185
  const pages = [];
73
186
  const errors = [];
74
- while (queue.length && pages.length < maxPages) {
75
- const url = queue.shift();
76
- const key = url.replace(/#.*$/, '');
77
- if (seen.has(key)) continue;
78
- seen.add(key);
187
+ let stopReason = 'frontier_exhausted';
188
+ const timeBudgetStopped = () => {
189
+ if (!timeExpired()) return false;
190
+ if (stopReason === 'frontier_exhausted') stopReason = 'time_budget_exhausted';
191
+ return true;
192
+ };
193
+
194
+ // Robots and sitemap topology are collected first so sitemap-only pages share
195
+ // the same bounded, deduplicated frontier as pages found through HTML links.
196
+ let robotsText = null;
197
+ if (!timeBudgetStopped()) {
79
198
  try {
80
- const res = await fetcher(url, { userAgent, maxBodyBytes: pageMaxBytes });
199
+ const robotsUrl = new URL('/robots.txt', origin).href;
200
+ const response = await fetcher(robotsUrl, { userAgent, maxBodyBytes: robotsMaxBytes });
201
+ if (response.status === 200) robotsText = response.body;
202
+ } catch {
203
+ // Missing robots remains represented by robotsText=null for existing detectors.
204
+ }
205
+ timeBudgetStopped();
206
+ }
207
+
208
+ const declaredSitemaps = robotsText != null ? parseRobots(robotsText).sitemaps : [];
209
+ const sitemapEntries = declaredSitemaps.length
210
+ ? declaredSitemaps
211
+ : [new URL('/sitemap.xml', origin).href];
212
+ const sitemapTopology = await collectSitemapTopology(sitemapEntries, {
213
+ fetcher,
214
+ maxDepth: sitemapMaxDepth,
215
+ maxDocuments: sitemapMaxDocuments,
216
+ maxUncompressedBytes: sitemapMaxUncompressedBytes,
217
+ maxTotalUncompressedBytes: sitemapMaxTotalUncompressedBytes,
218
+ maxDiscoveredUrls: sitemapMaxDiscoveredUrls,
219
+ maxQueuedDocuments: sitemapMaxQueuedDocuments,
220
+ maxRawEntries: sitemapMaxRawEntries,
221
+ maxXmlTokens: sitemapMaxXmlTokens,
222
+ sitemapMaxBytes,
223
+ origin,
224
+ userAgent,
225
+ shouldStop: timeExpired,
226
+ });
227
+ if (sitemapTopology.stop_reasons.includes('time_budget_exhausted')) timeBudgetStopped();
228
+ for (const error of sitemapTopology.errors) {
229
+ errors.push(sanitizedSitemapError(error));
230
+ }
231
+ const sitemaps = sitemapTopology.documents
232
+ .filter((document) => document.http_status === 200 && document.parsed?.rootValid)
233
+ .map((document) => ({
234
+ source: document.effective_url,
235
+ parsed: document.parsed,
236
+ status: document.status,
237
+ failure_reason: document.failure_reason,
238
+ }));
239
+
240
+ const seen = new Set();
241
+ const queued = new Set();
242
+ const queue = [];
243
+ const enqueuePage = (rawUrl, source, base = origin) => {
244
+ let url;
245
+ let requestedUrl;
246
+ try {
247
+ const raw = String(rawUrl).trim();
248
+ url = new URL(raw, base);
249
+ url.hash = '';
250
+ const fragmentAt = raw.indexOf('#');
251
+ const withoutFragment = fragmentAt === -1 ? raw : raw.slice(0, fragmentAt);
252
+ if (/^[A-Za-z][A-Za-z\d+.-]*:\/\//.test(withoutFragment)) requestedUrl = withoutFragment;
253
+ else if (withoutFragment.startsWith('//')) requestedUrl = `${new URL(base).protocol}${withoutFragment}`;
254
+ else requestedUrl = url.href;
255
+ } catch {
256
+ return;
257
+ }
258
+ if (url.origin !== origin || isProviderUtilityPath(url.pathname) || seen.has(url.href) || queued.has(url.href)) return;
259
+ coverageLedger.discover(url.href, source, { requested_url: requestedUrl });
260
+ queued.add(url.href);
261
+ queue.push({ url: url.href, requestedUrl, source });
262
+ };
263
+ enqueuePage(startUrl, 'start');
264
+ for (const entry of sitemapTopology.urls) enqueuePage(entry.url, 'sitemap');
265
+
266
+ while (queue.length && stopReason !== 'time_budget_exhausted') {
267
+ if (timeBudgetStopped()) break;
268
+ if (seen.size >= maxPages) {
269
+ stopReason = 'page_budget_exhausted';
270
+ break;
271
+ }
272
+ const next = queue.shift();
273
+ queued.delete(next.url);
274
+ seen.add(next.url);
275
+ coverageLedger.attempt(next.url, { attempt: 1, requested_url: next.requestedUrl });
276
+ try {
277
+ const response = await fetcher(next.url, { userAgent, maxBodyBytes: pageMaxBytes });
278
+ coverageLedger.retrieve(next.url, {
279
+ status: response.status,
280
+ effective_url: response.url,
281
+ body_complete: response.bodyComplete ?? true,
282
+ });
81
283
  const page = extractPage({
82
- url: res.url, html: res.body, status: res.status, headers: res.headers, redirectChain: res.redirectChain,
284
+ url: response.url, html: response.body, responseBody: response.bodyBytes ?? response.body, status: response.status,
285
+ headers: response.headers, redirectChain: response.redirectChain,
83
286
  });
84
- page.requestedUrl = url;
287
+ attachResourceMetadata(page, {
288
+ requestedUrl: next.requestedUrl,
289
+ bodyComplete: response.bodyComplete ?? true,
290
+ fetchAttempts: response.attempts ?? [],
291
+ });
292
+ page.crawlUrl = next.url;
293
+ if (page.resourceValidity?.state === 'valid_resource') {
294
+ coverageLedger.classify(next.url, {
295
+ state: 'valid_resource',
296
+ resource_id: page.urlIdentity?.resource_id,
297
+ requested_url: page.urlIdentity?.requested?.url ?? page.requestedUrl ?? null,
298
+ effective_url: page.urlIdentity?.effective?.url ?? page.url ?? null,
299
+ canonical_url: page.urlIdentity?.canonical?.url ?? null,
300
+ url_identity: page.urlIdentity,
301
+ response_body_hash: page.response_body_hash,
302
+ extracted_text_hash: page.extracted_text_hash,
303
+ structured_data_hash: page.structured_data_hash,
304
+ evidence_hash: page.evidence_hash,
305
+ artifact_hash: page.artifact_hash,
306
+ });
307
+ } else {
308
+ coverageLedger.markIndeterminate(next.url, {
309
+ reason_codes: page.resourceValidity?.reason_codes || ['resource_invalid'],
310
+ resource_validity: page.resourceValidity?.state || 'indeterminate',
311
+ });
312
+ }
313
+ page.discoverySource = next.source;
85
314
  pages.push(page);
86
- if (String(res.headers['content-type'] || '').includes('text/html')) {
87
- for (const l of page.links) {
88
- try {
89
- const u = new URL(l.href, res.url);
90
- u.hash = '';
91
- if (u.origin === origin && !isProviderUtilityPath(u.pathname) && !seen.has(u.href)) queue.push(u.href);
92
- } catch { /* unresolvable href — surfaced by LINK detectors */ }
315
+ if (timeBudgetStopped()) break;
316
+ if (String(response.headers['content-type'] || '').includes('text/html')) {
317
+ for (const link of page.links) {
318
+ if (timeBudgetStopped()) break;
319
+ enqueuePage(link.href, 'link', response.url);
93
320
  }
94
321
  }
95
- } catch (err) {
96
- errors.push(`${url}: ${err.message}`);
322
+ if (stopReason === 'time_budget_exhausted') break;
323
+ } catch (error) {
324
+ coverageLedger.fail(next.url, { reason: error?.code || 'FETCH_ERROR' });
325
+ errors.push(sanitizedFetchError(next.url, error));
97
326
  }
98
327
  }
99
- let robotsText = null;
100
- const sitemaps = [];
101
- try {
102
- const r = await fetcher(new URL('/robots.txt', origin).href, { userAgent, maxBodyBytes: robotsMaxBytes });
103
- if (r.status === 200) robotsText = r.body;
104
- } catch { /* recorded as missing robots */ }
105
- const smUrls = robotsText ? parseRobots(robotsText).sitemaps : [new URL('/sitemap.xml', origin).href];
106
- for (const sm of smUrls) {
107
- try {
108
- const sitemapUrl = new URL(sm, origin);
109
- if (sitemapUrl.origin !== origin) {
110
- errors.push(`${sitemapUrl.href}: sitemap URL leaves audited origin`);
111
- continue;
112
- }
113
- const r = await fetcher(sitemapUrl.href, { userAgent, maxBodyBytes: sitemapMaxBytes });
114
- if (r.status === 200) sitemaps.push({ source: sm, parsed: parseSitemap(r.body) });
115
- } catch { /* absence handled by TECH detectors */ }
116
- }
117
- const pendingUrls = [...new Set(queue.map((url) => url.replace(/#.*$/, '')).filter((url) => !seen.has(url)))];
328
+ if (stopReason === 'frontier_exhausted') timeBudgetStopped();
329
+ const pendingUrls = queue.map((entry) => entry.url).filter((url) => !seen.has(url));
118
330
  const crawl = {
119
331
  maxPages,
332
+ timeBudgetSeconds,
333
+ stopReason,
120
334
  pagesFetched: pages.length,
121
- truncated: pages.length >= maxPages && pendingUrls.length > 0,
335
+ truncated: stopReason === 'page_budget_exhausted' || stopReason === 'time_budget_exhausted',
122
336
  pendingUrlCount: pendingUrls.length,
123
337
  pendingUrls,
338
+ sitemapStopReasons: [...sitemapTopology.stop_reasons],
339
+ sitemapTopology: {
340
+ status: sitemapTopology.status,
341
+ stopReasons: [...sitemapTopology.stop_reasons],
342
+ limitations: [...sitemapTopology.limitations],
343
+ },
124
344
  };
125
345
  const site = assembleSite({ baseUrl: origin, pages, robotsText, sitemaps, transport: {}, mode: 'url', location: startUrl, crawl });
346
+ site.coverageLedger = coverageLedger;
347
+ site.coverageOptions = {
348
+ discoveryStatus: sitemapTopology.status,
349
+ discoveryMethods: ['start', 'link', 'sitemap'],
350
+ discoveryLimitations: [...sitemapTopology.limitations],
351
+ limitations: [...sitemapTopology.limitations],
352
+ };
353
+ site.previewCoverage = () => coverageLedger.preview(stopReason, site.coverageOptions);
354
+ site.finalizeCoverage = ({ evaluated = true } = {}) => {
355
+ if (evaluated) {
356
+ for (const page of pages) {
357
+ if (page.resourceValidity?.state === 'valid_resource') {
358
+ coverageLedger.evaluate(page.crawlUrl || page.url, {
359
+ evaluator: 'audit-detectors',
360
+ detector_scope: 'selected',
361
+ });
362
+ }
363
+ }
364
+ } else {
365
+ for (const page of pages) {
366
+ if (page.resourceValidity?.state === 'valid_resource') {
367
+ coverageLedger.markValidButUnevaluated(page.crawlUrl || page.url, { reason: 'not_evaluated' });
368
+ }
369
+ }
370
+ }
371
+ return coverageLedger.finalize(stopReason, site.coverageOptions);
372
+ };
126
373
  site.fetchErrors = errors;
374
+ site.sitemapTopology = sitemapTopology;
127
375
  return site;
128
376
  }
129
377
 
@@ -0,0 +1,83 @@
1
+ import { nowIso } from '../../shared/io.js';
2
+
3
+ export class CorrelationTracker {
4
+ constructor() {
5
+ this._sequence = 0;
6
+ this._startTime = performance.now();
7
+ this._currentStep = null; // { step_id, step_sequence, checkpoint_id }
8
+ this._phase = 'initial'; // 'initial' | 'during_step' | 'between_steps' | 'post_journey'
9
+ }
10
+
11
+ nextSequence() {
12
+ this._sequence += 1;
13
+ return this._sequence;
14
+ }
15
+
16
+ timing() {
17
+ const elapsed = Math.round((performance.now() - this._startTime) * 100) / 100;
18
+ return {
19
+ monotonic_offset_ms: elapsed,
20
+ timestamp: nowIso(),
21
+ };
22
+ }
23
+
24
+ startStep(step, stepSequence) {
25
+ this._currentStep = {
26
+ step_id: step.step_id,
27
+ step_sequence: stepSequence,
28
+ checkpoint_id: step.checkpoint_id || null,
29
+ };
30
+ this._phase = 'during_step';
31
+ }
32
+
33
+ endStep(step, stepSequence) {
34
+ this._phase = 'between_steps';
35
+ }
36
+
37
+ finishJourney() {
38
+ this._phase = 'post_journey';
39
+ }
40
+
41
+ /**
42
+ * Get correlation context for a newly initiated event/request.
43
+ */
44
+ getCorrelation(checkpointId = null) {
45
+ const stepId = this._currentStep ? this._currentStep.step_id : null;
46
+ return {
47
+ step_id: stepId,
48
+ step_sequence: this._currentStep ? this._currentStep.step_sequence : null,
49
+ checkpoint_id: checkpointId || (this._currentStep ? this._currentStep.checkpoint_id : null),
50
+ attribution_phase: this._phase,
51
+ initiated_step_id: stepId,
52
+ completed_step_id: null,
53
+ ambiguous_async: false,
54
+ };
55
+ }
56
+
57
+ /**
58
+ * Correlate completion of an asynchronous transaction (e.g. HTTP response).
59
+ * If the initiating step differs from the completing step/phase, mark as ambiguous_async.
60
+ */
61
+ correlateCompletion(initiatedCorrelation) {
62
+ const currentCorrelation = this.getCorrelation();
63
+ const isSameStep = initiatedCorrelation.step_id === currentCorrelation.step_id;
64
+ const isSamePhase = initiatedCorrelation.attribution_phase === currentCorrelation.attribution_phase;
65
+
66
+ let phase = initiatedCorrelation.attribution_phase;
67
+ let ambiguousAsync = false;
68
+ if (!isSameStep || (!isSamePhase && currentCorrelation.attribution_phase !== 'during_step')) {
69
+ phase = 'ambiguous_async';
70
+ ambiguousAsync = true;
71
+ }
72
+
73
+ return {
74
+ step_id: initiatedCorrelation.step_id,
75
+ step_sequence: initiatedCorrelation.step_sequence,
76
+ checkpoint_id: initiatedCorrelation.checkpoint_id,
77
+ attribution_phase: phase,
78
+ initiated_step_id: initiatedCorrelation.step_id,
79
+ completed_step_id: currentCorrelation.step_id,
80
+ ambiguous_async: ambiguousAsync,
81
+ };
82
+ }
83
+ }