@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,337 @@
1
+ import { safeJsonClone, isSensitiveKey, isSensitiveValue } from './sanitizer.js';
2
+
3
+ export class RuntimeEventCollector {
4
+ constructor(options = {}) {
5
+ const {
6
+ runtimeEventPolicy = {},
7
+ correlationTracker,
8
+ planId,
9
+ profileId,
10
+ } = options;
11
+
12
+ this.policy = runtimeEventPolicy;
13
+ this.enabled = Boolean(this.policy.enabled);
14
+ this.planId = planId || 'plan-unspecified';
15
+ this.profileId = profileId || 'profile-unspecified';
16
+ this.tracker = correlationTracker;
17
+
18
+ this.sources = this.policy.sources || [];
19
+ this.allowedEventNames = this.policy.allowed_event_names ? new Set(this.policy.allowed_event_names) : null;
20
+ this.allowedFields = this.policy.allowed_fields || null;
21
+ this.hashFields = this.policy.hash_fields || null;
22
+ this.redactFields = this.policy.redact_fields || null;
23
+ this.maxEvents = Math.min(this.policy.max_events || 200, 1000);
24
+ this.maxPayloadDepth = Math.min(this.policy.max_payload_depth || 5, 10);
25
+ this.maxFieldSize = Math.min(this.policy.max_field_size || 1024, 4096);
26
+
27
+ this.events = [];
28
+ this.eventCountsByName = {};
29
+ this.eventCountsBySource = {};
30
+ this.summary = {
31
+ total_events: 0,
32
+ filtered_events: 0,
33
+ by_source: {},
34
+ by_event_name: {},
35
+ collector_status: 'complete',
36
+ truncated: false,
37
+ truncation_reason: null,
38
+ collector_health: {
39
+ status: 'active',
40
+ queue_replaced: false,
41
+ push_replaced: false,
42
+ polling_fallback_used: false,
43
+ supported_contexts: ['top_level_main_frame'],
44
+ unsupported_contexts: ['cross_origin_iframes', 'web_workers', 'service_workers'],
45
+ },
46
+ };
47
+ }
48
+
49
+ /**
50
+ * Generates initialization script to be added to browser context or page.
51
+ */
52
+ getInitScript() {
53
+ if (!this.enabled || !this.sources.length) return null;
54
+ const sourcesConfig = JSON.stringify(this.sources);
55
+
56
+ return `
57
+ (function() {
58
+ window.__citable_events = window.__citable_events || [];
59
+ window.__citable_health = window.__citable_health || {
60
+ queue_replaced: false,
61
+ push_replaced: false,
62
+ polling_fallback_used: false,
63
+ };
64
+ const sources = ${sourcesConfig};
65
+
66
+ function safeExtract(obj, depth, seen) {
67
+ if (depth <= 0) return '[DEPTH_LIMIT]';
68
+ if (obj === null || obj === undefined) return obj;
69
+ const t = typeof obj;
70
+ if (t === 'number' || t === 'boolean' || t === 'string') return obj;
71
+ if (t !== 'object') return String(obj);
72
+ if (seen.has(obj)) return '[CIRCULAR]';
73
+ seen.add(obj);
74
+
75
+ if (Array.isArray(obj)) {
76
+ return obj.map(function(item) { return safeExtract(item, depth - 1, seen); });
77
+ }
78
+
79
+ const res = {};
80
+ const keys = Object.keys(obj);
81
+ for (var i = 0; i < keys.length; i++) {
82
+ var k = keys[i];
83
+ if (k === '__proto__' || k === 'constructor' || k === 'prototype') continue;
84
+ try {
85
+ res[k] = safeExtract(obj[k], depth - 1, seen);
86
+ } catch (err) {
87
+ res[k] = '[GETTER_ERROR]';
88
+ }
89
+ }
90
+ return res;
91
+ }
92
+
93
+ for (var i = 0; i < sources.length; i++) {
94
+ var src = sources[i];
95
+ if (src.type === 'global_array' && src.global_name) {
96
+ var propName = src.global_name.replace(/^window\\./, '');
97
+
98
+ (function(sourceConfig, prop) {
99
+ var currentArray = window[prop];
100
+ if (!Array.isArray(currentArray)) {
101
+ currentArray = [];
102
+ window[prop] = currentArray;
103
+ }
104
+
105
+ for (var j = 0; j < currentArray.length; j++) {
106
+ try {
107
+ window.__citable_events.push({
108
+ source_id: sourceConfig.source_id,
109
+ payload: safeExtract(currentArray[j], 5, new WeakSet()),
110
+ ts: Date.now()
111
+ });
112
+ } catch (e) {}
113
+ }
114
+ currentArray.__citable_drained_count = currentArray.length;
115
+
116
+ function wrapPush(arr) {
117
+ var origPush = arr.push;
118
+ arr.push = function(...items) {
119
+ for (var k = 0; k < items.length; k++) {
120
+ try {
121
+ window.__citable_events.push({
122
+ source_id: sourceConfig.source_id,
123
+ payload: safeExtract(items[k], 5, new WeakSet()),
124
+ ts: Date.now()
125
+ });
126
+ } catch (e) {}
127
+ }
128
+ if (typeof origPush === 'function') {
129
+ return origPush.apply(this, items);
130
+ }
131
+ };
132
+ }
133
+
134
+ wrapPush(currentArray);
135
+
136
+ try {
137
+ var storedArray = currentArray;
138
+ Object.defineProperty(window, prop, {
139
+ configurable: true,
140
+ enumerable: true,
141
+ get: function() { return storedArray; },
142
+ set: function(newVal) {
143
+ window.__citable_health.queue_replaced = true;
144
+ storedArray = newVal;
145
+ if (Array.isArray(newVal)) {
146
+ wrapPush(newVal);
147
+ for (var m = 0; m < newVal.length; m++) {
148
+ try {
149
+ window.__citable_events.push({
150
+ source_id: sourceConfig.source_id,
151
+ payload: safeExtract(newVal[m], 5, new WeakSet()),
152
+ ts: Date.now()
153
+ });
154
+ } catch (e) {}
155
+ }
156
+ newVal.__citable_drained_count = newVal.length;
157
+ }
158
+ }
159
+ });
160
+ } catch (e) {}
161
+ })(src, propName);
162
+
163
+ } else if (src.type === 'window_event' && src.event_name) {
164
+ (function(sourceConfig) {
165
+ window.addEventListener(sourceConfig.event_name, function(e) {
166
+ try {
167
+ window.__citable_events.push({
168
+ source_id: sourceConfig.source_id,
169
+ payload: {
170
+ type: e.type,
171
+ detail: safeExtract(e.detail, 5, new WeakSet()) || null,
172
+ },
173
+ ts: Date.now()
174
+ });
175
+ } catch (err) {}
176
+ }, true);
177
+ })(src);
178
+ }
179
+ }
180
+ })();
181
+ `;
182
+ }
183
+
184
+ async installOnPage(page) {
185
+ if (!this.enabled || !this.sources.length) return;
186
+ const script = this.getInitScript();
187
+ if (!script) return;
188
+ try {
189
+ await page.evaluate(script);
190
+ } catch {
191
+ // Ignored if page is not ready yet
192
+ }
193
+ }
194
+
195
+ async drainEvents(page) {
196
+ if (!this.enabled || !this.sources.length) return;
197
+
198
+ let drainResult = null;
199
+ try {
200
+ drainResult = await page.evaluate((sources) => {
201
+ const events = window.__citable_events ? window.__citable_events.splice(0) : [];
202
+ const health = window.__citable_health || {
203
+ queue_replaced: false,
204
+ push_replaced: false,
205
+ polling_fallback_used: false,
206
+ };
207
+
208
+ // Fallback polling for arrays where push was replaced or unhooked
209
+ for (const src of sources) {
210
+ if (src.type === 'global_array' && src.global_name) {
211
+ const prop = src.global_name.replace(/^window\./, '');
212
+ const arr = window[prop];
213
+ if (Array.isArray(arr)) {
214
+ const startIdx = arr.__citable_drained_count || 0;
215
+ if (arr.length > startIdx) {
216
+ health.polling_fallback_used = true;
217
+ for (let i = startIdx; i < arr.length; i++) {
218
+ events.push({
219
+ source_id: src.source_id,
220
+ payload: arr[i],
221
+ ts: Date.now(),
222
+ });
223
+ }
224
+ arr.__citable_drained_count = arr.length;
225
+ }
226
+ }
227
+ }
228
+ }
229
+
230
+ return { events, health };
231
+ }, this.sources);
232
+ } catch {
233
+ return;
234
+ }
235
+
236
+ const eventsList = Array.isArray(drainResult)
237
+ ? drainResult
238
+ : (drainResult && Array.isArray(drainResult.events) ? drainResult.events : []);
239
+
240
+ if (!eventsList || eventsList.length === 0) return;
241
+
242
+ if (drainResult && !Array.isArray(drainResult) && drainResult.health) {
243
+ if (drainResult.health.queue_replaced) this.summary.collector_health.queue_replaced = true;
244
+ if (drainResult.health.push_replaced) this.summary.collector_health.push_replaced = true;
245
+ if (drainResult.health.polling_fallback_used) this.summary.collector_health.polling_fallback_used = true;
246
+ }
247
+
248
+ for (const raw of eventsList) {
249
+ if (this.events.length >= this.maxEvents) {
250
+ this.summary.truncated = true;
251
+ this.summary.truncation_reason = 'max_events_exceeded';
252
+ break;
253
+ }
254
+
255
+ const sourceId = raw.source_id || 'unknown';
256
+ const sourceConfig = this.sources.find((s) => s.source_id === sourceId) || {};
257
+ const payload = raw.payload;
258
+
259
+ // Extract event name
260
+ let eventName = 'unnamed_event';
261
+ if (typeof payload === 'object' && payload !== null) {
262
+ const keyName = sourceConfig.event_name_key || 'event';
263
+ if (typeof payload[keyName] === 'string') {
264
+ eventName = payload[keyName];
265
+ } else if (typeof payload.type === 'string') {
266
+ eventName = payload.type;
267
+ } else if (sourceConfig.event_name) {
268
+ eventName = sourceConfig.event_name;
269
+ }
270
+ } else if (typeof payload === 'string') {
271
+ eventName = payload;
272
+ }
273
+
274
+ // Check allowed event names
275
+ const isAllowed = !this.allowedEventNames || this.allowedEventNames.has(eventName);
276
+ if (!isAllowed) {
277
+ this.summary.filtered_events++;
278
+ continue; // Filtered out
279
+ }
280
+
281
+ this.eventCountsByName[eventName] = (this.eventCountsByName[eventName] || 0) + 1;
282
+ this.eventCountsBySource[sourceId] = (this.eventCountsBySource[sourceId] || 0) + 1;
283
+ const occurrenceIndex = this.eventCountsByName[eventName];
284
+
285
+ const seq = this.tracker.nextSequence();
286
+ const timing = this.tracker.timing();
287
+ const correlation = this.tracker.getCorrelation();
288
+
289
+ // Sanitize payload
290
+ const stats = { redactedCount: 0, filteredFields: [] };
291
+ const sanitizedPayload = typeof payload === 'object' && payload !== null
292
+ ? safeJsonClone(payload, {
293
+ maxDepth: this.maxPayloadDepth,
294
+ maxFieldSize: this.maxFieldSize,
295
+ allowedFields: this.allowedFields,
296
+ hashFields: this.hashFields,
297
+ redactFields: this.redactFields,
298
+ stats,
299
+ })
300
+ : { value: String(payload) };
301
+
302
+ const eventRecord = {
303
+ sequence: seq,
304
+ source_id: sourceId,
305
+ event_name: eventName,
306
+ occurrence_index: occurrenceIndex,
307
+ timing,
308
+ correlation,
309
+ payload: sanitizedPayload,
310
+ disposition: {
311
+ capture_status: stats.redactedCount > 0 ? 'redacted' : 'captured',
312
+ redacted_count: stats.redactedCount,
313
+ filtered_fields: stats.filteredFields,
314
+ },
315
+ };
316
+
317
+ this.events.push(eventRecord);
318
+ this.summary.total_events++;
319
+ }
320
+
321
+ this.summary.by_source = { ...this.eventCountsBySource };
322
+ this.summary.by_event_name = { ...this.eventCountsByName };
323
+ }
324
+
325
+ toArtifact() {
326
+ this.summary.collector_status = this.summary.truncated ? 'truncated' : 'complete';
327
+ return {
328
+ schema_version: 1,
329
+ plan_id: this.planId,
330
+ profile_id: this.profileId,
331
+ collected_at: this.tracker.timing().timestamp,
332
+ capture_policy: this.policy,
333
+ summary: this.summary,
334
+ events: this.events,
335
+ };
336
+ }
337
+ }
@@ -0,0 +1,322 @@
1
+ import { sha256 } from '../../shared/io.js';
2
+
3
+ // Sensitive key patterns: headers, storage keys, query parameters, payload fields
4
+ export const SENSITIVE_KEY_PATTERN = /(?:^|[-_])(?:authorization|bearer|token|secret|apiKey|api[-_]?key|password|credential|access[-_]?token|refresh[-_]?token|jwt|session(?:[-_]?id)?|csrf|xsrf|cvv|cvc|ssn|credit[-_]?card|passwd|auth[-_]?token|oauth[-_]?token|auth[-_]?code|code)(?:$|[-_])|^(?:cookie|set[-_]?cookie)$/i;
5
+
6
+ // Sensitive value patterns: bearer tokens, common API keys, JWT tokens, card numbers
7
+ export const SENSITIVE_VALUE_PATTERN = /(?:bearer\s+[a-zA-Z0-9_\-.]{10,}|(?:ghp|gho|glpat|ya29|sk-[a-zA-Z0-9]{20,})|eyJ[a-zA-Z0-9_-]{10,}\.eyJ[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}|\b(?:\d[ -]*?){13,16}\b)/i;
8
+
9
+ export const REDACTED_MARKER = '[REDACTED_SECRET]';
10
+ export const FILTERED_MARKER = '[FILTERED]';
11
+ export const POLICY_RESTRICTED_MARKER = '[POLICY_RESTRICTED]';
12
+
13
+ /**
14
+ * Check if a field/header/cookie/param key is sensitive.
15
+ */
16
+ export function isSensitiveKey(key) {
17
+ if (typeof key !== 'string') return false;
18
+ return SENSITIVE_KEY_PATTERN.test(key);
19
+ }
20
+
21
+ /**
22
+ * Check if a string value contains sensitive tokens or patterns.
23
+ */
24
+ export function isSensitiveValue(value) {
25
+ if (typeof value !== 'string') return false;
26
+ return SENSITIVE_VALUE_PATTERN.test(value);
27
+ }
28
+
29
+ /**
30
+ * Sanitize headers against allowlist and sensitive key/value rules.
31
+ * Even if a sensitive header is allowlisted, it MUST be redacted.
32
+ */
33
+ export function sanitizeHeaders(headers, allowedNames = null) {
34
+ if (!headers || typeof headers !== 'object') {
35
+ return { headers: null, status: 'not_requested', redactedCount: 0, filteredCount: 0 };
36
+ }
37
+ const allowedSet = Array.isArray(allowedNames) ? new Set(allowedNames.map((n) => n.toLowerCase())) : null;
38
+ const result = {};
39
+ let redactedCount = 0;
40
+ let filteredCount = 0;
41
+
42
+ for (const [rawKey, rawVal] of Object.entries(headers)) {
43
+ const key = rawKey.toLowerCase();
44
+ // Sensitive keys are unconditionally redacted or omitted
45
+ if (isSensitiveKey(key)) {
46
+ if (allowedSet && allowedSet.has(key)) {
47
+ result[key] = REDACTED_MARKER;
48
+ redactedCount++;
49
+ } else {
50
+ filteredCount++;
51
+ }
52
+ continue;
53
+ }
54
+
55
+ // Non-sensitive keys: must be explicitly allowed if allowedSet is provided
56
+ if (allowedSet && !allowedSet.has(key)) {
57
+ filteredCount++;
58
+ continue;
59
+ }
60
+
61
+ // Value sanitization
62
+ const strVal = String(rawVal ?? '');
63
+ if (isSensitiveValue(strVal)) {
64
+ result[key] = REDACTED_MARKER;
65
+ redactedCount++;
66
+ } else {
67
+ result[key] = strVal;
68
+ }
69
+ }
70
+
71
+ const hasEntries = Object.keys(result).length > 0;
72
+ let status = 'captured';
73
+ if (!hasEntries) {
74
+ status = filteredCount > 0 ? 'filtered' : 'not_requested';
75
+ } else if (redactedCount > 0) {
76
+ status = 'redacted';
77
+ }
78
+
79
+ return {
80
+ headers: hasEntries ? result : null,
81
+ status,
82
+ redactedCount,
83
+ filteredCount,
84
+ };
85
+ }
86
+
87
+ /**
88
+ * Sanitize URL query parameters according to allowlist.
89
+ */
90
+ export function sanitizeQueryParams(urlStr, allowedParams = null) {
91
+ if (!urlStr) return { query: null, status: 'none_present', redactedCount: 0, filteredFields: [] };
92
+ if (!allowedParams || !Array.isArray(allowedParams) || allowedParams.length === 0) {
93
+ return { query: null, status: 'not_requested', redactedCount: 0, filteredFields: [] };
94
+ }
95
+ let parsed;
96
+ try {
97
+ parsed = new URL(urlStr);
98
+ } catch {
99
+ return { query: null, status: 'none_present', redactedCount: 0, filteredFields: [] };
100
+ }
101
+
102
+ const searchParams = parsed.searchParams;
103
+ if (!searchParams || Array.from(searchParams.keys()).length === 0) {
104
+ return { query: null, status: 'none_present', redactedCount: 0, filteredFields: [] };
105
+ }
106
+
107
+ const allowedSet = new Set(allowedParams.map((p) => p.toLowerCase()));
108
+ const query = {};
109
+ let redactedCount = 0;
110
+ const filteredFields = [];
111
+
112
+ for (const [key, val] of searchParams.entries()) {
113
+ const lowerKey = key.toLowerCase();
114
+ if (isSensitiveKey(lowerKey)) {
115
+ if (allowedSet.has(lowerKey)) {
116
+ query[key] = REDACTED_MARKER;
117
+ redactedCount++;
118
+ } else {
119
+ filteredFields.push(key);
120
+ }
121
+ continue;
122
+ }
123
+
124
+ if (!allowedSet.has(lowerKey)) {
125
+ filteredFields.push(key);
126
+ continue;
127
+ }
128
+
129
+ if (isSensitiveValue(val)) {
130
+ query[key] = REDACTED_MARKER;
131
+ redactedCount++;
132
+ } else {
133
+ query[key] = val;
134
+ }
135
+ }
136
+
137
+ const hasEntries = Object.keys(query).length > 0;
138
+ let status = 'captured';
139
+ if (!hasEntries) {
140
+ status = filteredFields.length > 0 ? 'filtered' : 'none_present';
141
+ } else if (redactedCount > 0) {
142
+ status = 'redacted';
143
+ }
144
+
145
+ return {
146
+ query: hasEntries ? query : null,
147
+ status,
148
+ redactedCount,
149
+ filteredFields,
150
+ };
151
+ }
152
+
153
+ /**
154
+ * Safely deep-clone an object with cycle detection, depth limits, field allowlists,
155
+ * and sensitive-value redaction.
156
+ */
157
+ export function safeJsonClone(value, options = {}) {
158
+ const {
159
+ maxDepth = 5,
160
+ maxBytes = 4096,
161
+ maxFieldSize = 1024,
162
+ allowedFields = null,
163
+ hashFields = null,
164
+ redactFields = null,
165
+ seen = new WeakSet(),
166
+ depth = 0,
167
+ stats = { redactedCount: 0, filteredFields: [] },
168
+ } = options;
169
+
170
+ if (value === null || value === undefined) return value;
171
+ if (typeof value === 'boolean' || typeof value === 'number') return value;
172
+
173
+ if (typeof value === 'string') {
174
+ if (isSensitiveValue(value)) {
175
+ stats.redactedCount++;
176
+ return REDACTED_MARKER;
177
+ }
178
+ if (value.length > maxFieldSize) {
179
+ return value.slice(0, maxFieldSize) + '...[TRUNCATED]';
180
+ }
181
+ return value;
182
+ }
183
+
184
+ if (depth >= maxDepth) return '[DEPTH_LIMIT]';
185
+
186
+ if (typeof value === 'object') {
187
+ if (seen.has(value)) return '[CIRCULAR]';
188
+ seen.add(value);
189
+
190
+ if (Array.isArray(value)) {
191
+ return value.map((item) => safeJsonClone(item, { ...options, depth: depth + 1, seen, stats }));
192
+ }
193
+
194
+ const copy = {};
195
+ const allowedSet = allowedFields ? new Set(allowedFields) : null;
196
+ const hashSet = hashFields ? new Set(hashFields) : null;
197
+ const redactSet = redactFields ? new Set(redactFields) : null;
198
+
199
+ for (const k of Object.getOwnPropertyNames(value)) {
200
+ // Check allowlist first: if not allowed, filter it out
201
+ if (allowedSet && !allowedSet.has(k)) {
202
+ stats.filteredFields.push(k);
203
+ continue;
204
+ }
205
+
206
+ // If allowed (or no allowlist specified), check if k is sensitive or in redactSet
207
+ if (isSensitiveKey(k) || (redactSet && redactSet.has(k))) {
208
+ copy[k] = REDACTED_MARKER;
209
+ stats.redactedCount++;
210
+ continue;
211
+ }
212
+
213
+ let v;
214
+ try {
215
+ v = value[k];
216
+ } catch {
217
+ v = '[GETTER_ERROR]';
218
+ }
219
+
220
+ // Check hash fields
221
+ if (hashSet && hashSet.has(k)) {
222
+ copy[k] = `sha256:${sha256(typeof v === 'string' ? v : JSON.stringify(v))}`;
223
+ continue;
224
+ }
225
+
226
+ copy[k] = safeJsonClone(v, { ...options, depth: depth + 1, seen, stats });
227
+ }
228
+ return copy;
229
+ }
230
+
231
+ return String(value);
232
+ }
233
+
234
+ /**
235
+ * Sanitize request or response payloads (body).
236
+ */
237
+ export function sanitizePayload(payload, options = {}) {
238
+ const {
239
+ allowedFields = null,
240
+ hashOnly = false,
241
+ maxBytes = 4096,
242
+ maxDepth = 5,
243
+ } = options;
244
+
245
+ if (payload == null || payload === '') {
246
+ return { sanitized: null, hash: null, bytes: 0, status: 'unavailable', redactedCount: 0, filteredFields: [] };
247
+ }
248
+
249
+ // Handle binary buffers without unsafe UTF-8 string coercion
250
+ if (Buffer.isBuffer(payload)) {
251
+ const isBinary = payload.some((byte) => byte === 0);
252
+ if (isBinary) {
253
+ const hash = sha256(payload);
254
+ return {
255
+ sanitized: null,
256
+ hash,
257
+ bytes: payload.length,
258
+ status: hashOnly ? 'hash_only' : 'unavailable',
259
+ redactedCount: 0,
260
+ filteredFields: [],
261
+ };
262
+ }
263
+ }
264
+
265
+ const rawString = typeof payload === 'string' ? payload : Buffer.isBuffer(payload) ? payload.toString('utf8') : JSON.stringify(payload);
266
+ const bytes = Buffer.byteLength(rawString, 'utf8');
267
+ const hash = sha256(rawString);
268
+
269
+ if (hashOnly) {
270
+ return { sanitized: null, hash, bytes, status: 'hash_only', redactedCount: 0, filteredFields: [] };
271
+ }
272
+
273
+ // Try parsing JSON if allowedFields or structured redaction is requested
274
+ let parsedJson = null;
275
+ try {
276
+ parsedJson = JSON.parse(rawString);
277
+ } catch {
278
+ parsedJson = null;
279
+ }
280
+
281
+ const stats = { redactedCount: 0, filteredFields: [] };
282
+
283
+ if (parsedJson && typeof parsedJson === 'object') {
284
+ const cloned = safeJsonClone(parsedJson, {
285
+ maxDepth,
286
+ maxBytes,
287
+ allowedFields,
288
+ stats,
289
+ });
290
+ const hasAllowedEntries = cloned !== null && typeof cloned === 'object' && Object.keys(cloned).length > 0;
291
+ let status = 'captured';
292
+ if (!hasAllowedEntries && allowedFields && allowedFields.length > 0) {
293
+ status = 'filtered';
294
+ } else if (rawString.length > maxBytes) {
295
+ status = 'truncated';
296
+ }
297
+ return {
298
+ sanitized: cloned,
299
+ hash,
300
+ bytes,
301
+ status,
302
+ redactedCount: stats.redactedCount,
303
+ filteredFields: stats.filteredFields,
304
+ };
305
+ }
306
+
307
+ // Raw text payload
308
+ if (isSensitiveValue(rawString)) {
309
+ return { sanitized: REDACTED_MARKER, hash, bytes, status: 'captured', redactedCount: 1, filteredFields: [] };
310
+ }
311
+
312
+ const isTruncated = rawString.length > maxBytes;
313
+ const truncated = isTruncated ? rawString.slice(0, maxBytes) + '...[TRUNCATED]' : rawString;
314
+ return {
315
+ sanitized: truncated,
316
+ hash,
317
+ bytes,
318
+ status: isTruncated ? 'truncated' : 'captured',
319
+ redactedCount: 0,
320
+ filteredFields: [],
321
+ };
322
+ }