@nebulacomponents/citable 1.13.1 → 1.14.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 (305) hide show
  1. package/CHANGELOG.md +246 -0
  2. package/README.md +43 -5
  3. package/dist/universal/.agents/skills/citable/SKILL.md +13 -4
  4. package/dist/universal/.agents/skills/citable/VERSION +1 -1
  5. package/dist/universal/.agents/skills/citable/agents/citable-auditor.md +64 -0
  6. package/dist/universal/.agents/skills/citable/agents/citable-semantic-reviewer.md +66 -0
  7. package/dist/universal/.agents/skills/citable/commands/action-findings.md +9 -1
  8. package/dist/universal/.agents/skills/citable/commands/observe-and-act.md +50 -3
  9. package/dist/universal/.agents/skills/citable/commands/plan-audit.md +35 -0
  10. package/dist/universal/.agents/skills/citable/manifest.json +26 -14
  11. package/dist/universal/.agents/skills/citable/schemas/action-plan.schema.json +13 -0
  12. package/dist/universal/.agents/skills/citable/schemas/browser-evidence-plan.schema.json +14 -0
  13. package/dist/universal/.agents/skills/citable/schemas/canonical-index-import.schema.json +30 -0
  14. package/dist/universal/.agents/skills/citable/schemas/common.defs.json +12 -1
  15. package/dist/universal/.agents/skills/citable/schemas/corroboration-import.schema.json +48 -0
  16. package/dist/universal/.agents/skills/citable/schemas/entity-graph.schema.json +75 -0
  17. package/dist/universal/.agents/skills/citable/schemas/entity.schema.json +13 -0
  18. package/dist/universal/.agents/skills/citable/schemas/observation.schema.json +1 -1
  19. package/dist/universal/.agents/skills/citable/schemas/page.schema.json +3 -0
  20. package/dist/universal/.agents/skills/citable/schemas/regional-network-import.schema.json +55 -0
  21. package/dist/universal/.agents/skills/citable/schemas/source-identity-chain.schema.json +83 -0
  22. package/dist/universal/.agents/skills/citable/scripts/README.md +3 -1
  23. package/dist/universal/.claude/agents/citable/citable-auditor.md +64 -0
  24. package/dist/universal/.claude/agents/citable/citable-semantic-reviewer.md +66 -0
  25. package/dist/universal/.claude/agents/citable/manifest.json +12 -0
  26. package/dist/universal/.claude/skills/citable/SKILL.md +13 -4
  27. package/dist/universal/.claude/skills/citable/VERSION +1 -1
  28. package/dist/universal/.claude/skills/citable/agents/citable-auditor.md +64 -0
  29. package/dist/universal/.claude/skills/citable/agents/citable-semantic-reviewer.md +66 -0
  30. package/dist/universal/.claude/skills/citable/commands/action-findings.md +9 -1
  31. package/dist/universal/.claude/skills/citable/commands/observe-and-act.md +50 -3
  32. package/dist/universal/.claude/skills/citable/commands/plan-audit.md +35 -0
  33. package/dist/universal/.claude/skills/citable/manifest.json +31 -14
  34. package/dist/universal/.claude/skills/citable/schemas/action-plan.schema.json +13 -0
  35. package/dist/universal/.claude/skills/citable/schemas/browser-evidence-plan.schema.json +14 -0
  36. package/dist/universal/.claude/skills/citable/schemas/canonical-index-import.schema.json +30 -0
  37. package/dist/universal/.claude/skills/citable/schemas/common.defs.json +12 -1
  38. package/dist/universal/.claude/skills/citable/schemas/corroboration-import.schema.json +48 -0
  39. package/dist/universal/.claude/skills/citable/schemas/entity-graph.schema.json +75 -0
  40. package/dist/universal/.claude/skills/citable/schemas/entity.schema.json +13 -0
  41. package/dist/universal/.claude/skills/citable/schemas/observation.schema.json +1 -1
  42. package/dist/universal/.claude/skills/citable/schemas/page.schema.json +3 -0
  43. package/dist/universal/.claude/skills/citable/schemas/regional-network-import.schema.json +55 -0
  44. package/dist/universal/.claude/skills/citable/schemas/source-identity-chain.schema.json +83 -0
  45. package/dist/universal/.claude/skills/citable/scripts/README.md +3 -1
  46. package/dist/universal/.cursor/skills/citable/SKILL.md +13 -4
  47. package/dist/universal/.cursor/skills/citable/VERSION +1 -1
  48. package/dist/universal/.cursor/skills/citable/agents/citable-auditor.md +64 -0
  49. package/dist/universal/.cursor/skills/citable/agents/citable-semantic-reviewer.md +66 -0
  50. package/dist/universal/.cursor/skills/citable/commands/action-findings.md +9 -1
  51. package/dist/universal/.cursor/skills/citable/commands/observe-and-act.md +50 -3
  52. package/dist/universal/.cursor/skills/citable/commands/plan-audit.md +35 -0
  53. package/dist/universal/.cursor/skills/citable/manifest.json +26 -14
  54. package/dist/universal/.cursor/skills/citable/schemas/action-plan.schema.json +13 -0
  55. package/dist/universal/.cursor/skills/citable/schemas/browser-evidence-plan.schema.json +14 -0
  56. package/dist/universal/.cursor/skills/citable/schemas/canonical-index-import.schema.json +30 -0
  57. package/dist/universal/.cursor/skills/citable/schemas/common.defs.json +12 -1
  58. package/dist/universal/.cursor/skills/citable/schemas/corroboration-import.schema.json +48 -0
  59. package/dist/universal/.cursor/skills/citable/schemas/entity-graph.schema.json +75 -0
  60. package/dist/universal/.cursor/skills/citable/schemas/entity.schema.json +13 -0
  61. package/dist/universal/.cursor/skills/citable/schemas/observation.schema.json +1 -1
  62. package/dist/universal/.cursor/skills/citable/schemas/page.schema.json +3 -0
  63. package/dist/universal/.cursor/skills/citable/schemas/regional-network-import.schema.json +55 -0
  64. package/dist/universal/.cursor/skills/citable/schemas/source-identity-chain.schema.json +83 -0
  65. package/dist/universal/.cursor/skills/citable/scripts/README.md +3 -1
  66. package/dist/universal/.gemini/skills/citable/SKILL.md +13 -4
  67. package/dist/universal/.gemini/skills/citable/VERSION +1 -1
  68. package/dist/universal/.gemini/skills/citable/agents/citable-auditor.md +64 -0
  69. package/dist/universal/.gemini/skills/citable/agents/citable-semantic-reviewer.md +66 -0
  70. package/dist/universal/.gemini/skills/citable/commands/action-findings.md +9 -1
  71. package/dist/universal/.gemini/skills/citable/commands/observe-and-act.md +50 -3
  72. package/dist/universal/.gemini/skills/citable/commands/plan-audit.md +35 -0
  73. package/dist/universal/.gemini/skills/citable/manifest.json +26 -14
  74. package/dist/universal/.gemini/skills/citable/schemas/action-plan.schema.json +13 -0
  75. package/dist/universal/.gemini/skills/citable/schemas/browser-evidence-plan.schema.json +14 -0
  76. package/dist/universal/.gemini/skills/citable/schemas/canonical-index-import.schema.json +30 -0
  77. package/dist/universal/.gemini/skills/citable/schemas/common.defs.json +12 -1
  78. package/dist/universal/.gemini/skills/citable/schemas/corroboration-import.schema.json +48 -0
  79. package/dist/universal/.gemini/skills/citable/schemas/entity-graph.schema.json +75 -0
  80. package/dist/universal/.gemini/skills/citable/schemas/entity.schema.json +13 -0
  81. package/dist/universal/.gemini/skills/citable/schemas/observation.schema.json +1 -1
  82. package/dist/universal/.gemini/skills/citable/schemas/page.schema.json +3 -0
  83. package/dist/universal/.gemini/skills/citable/schemas/regional-network-import.schema.json +55 -0
  84. package/dist/universal/.gemini/skills/citable/schemas/source-identity-chain.schema.json +83 -0
  85. package/dist/universal/.gemini/skills/citable/scripts/README.md +3 -1
  86. package/dist/universal/.github/skills/citable/SKILL.md +13 -4
  87. package/dist/universal/.github/skills/citable/VERSION +1 -1
  88. package/dist/universal/.github/skills/citable/agents/citable-auditor.md +64 -0
  89. package/dist/universal/.github/skills/citable/agents/citable-semantic-reviewer.md +66 -0
  90. package/dist/universal/.github/skills/citable/commands/action-findings.md +9 -1
  91. package/dist/universal/.github/skills/citable/commands/observe-and-act.md +50 -3
  92. package/dist/universal/.github/skills/citable/commands/plan-audit.md +35 -0
  93. package/dist/universal/.github/skills/citable/manifest.json +26 -14
  94. package/dist/universal/.github/skills/citable/schemas/action-plan.schema.json +13 -0
  95. package/dist/universal/.github/skills/citable/schemas/browser-evidence-plan.schema.json +14 -0
  96. package/dist/universal/.github/skills/citable/schemas/canonical-index-import.schema.json +30 -0
  97. package/dist/universal/.github/skills/citable/schemas/common.defs.json +12 -1
  98. package/dist/universal/.github/skills/citable/schemas/corroboration-import.schema.json +48 -0
  99. package/dist/universal/.github/skills/citable/schemas/entity-graph.schema.json +75 -0
  100. package/dist/universal/.github/skills/citable/schemas/entity.schema.json +13 -0
  101. package/dist/universal/.github/skills/citable/schemas/observation.schema.json +1 -1
  102. package/dist/universal/.github/skills/citable/schemas/page.schema.json +3 -0
  103. package/dist/universal/.github/skills/citable/schemas/regional-network-import.schema.json +55 -0
  104. package/dist/universal/.github/skills/citable/schemas/source-identity-chain.schema.json +83 -0
  105. package/dist/universal/.github/skills/citable/scripts/README.md +3 -1
  106. package/dist/universal/.kiro/skills/citable/SKILL.md +13 -4
  107. package/dist/universal/.kiro/skills/citable/VERSION +1 -1
  108. package/dist/universal/.kiro/skills/citable/agents/citable-auditor.md +64 -0
  109. package/dist/universal/.kiro/skills/citable/agents/citable-semantic-reviewer.md +66 -0
  110. package/dist/universal/.kiro/skills/citable/commands/action-findings.md +9 -1
  111. package/dist/universal/.kiro/skills/citable/commands/observe-and-act.md +50 -3
  112. package/dist/universal/.kiro/skills/citable/commands/plan-audit.md +35 -0
  113. package/dist/universal/.kiro/skills/citable/manifest.json +26 -14
  114. package/dist/universal/.kiro/skills/citable/schemas/action-plan.schema.json +13 -0
  115. package/dist/universal/.kiro/skills/citable/schemas/browser-evidence-plan.schema.json +14 -0
  116. package/dist/universal/.kiro/skills/citable/schemas/canonical-index-import.schema.json +30 -0
  117. package/dist/universal/.kiro/skills/citable/schemas/common.defs.json +12 -1
  118. package/dist/universal/.kiro/skills/citable/schemas/corroboration-import.schema.json +48 -0
  119. package/dist/universal/.kiro/skills/citable/schemas/entity-graph.schema.json +75 -0
  120. package/dist/universal/.kiro/skills/citable/schemas/entity.schema.json +13 -0
  121. package/dist/universal/.kiro/skills/citable/schemas/observation.schema.json +1 -1
  122. package/dist/universal/.kiro/skills/citable/schemas/page.schema.json +3 -0
  123. package/dist/universal/.kiro/skills/citable/schemas/regional-network-import.schema.json +55 -0
  124. package/dist/universal/.kiro/skills/citable/schemas/source-identity-chain.schema.json +83 -0
  125. package/dist/universal/.kiro/skills/citable/scripts/README.md +3 -1
  126. package/dist/universal/.opencode/skills/citable/SKILL.md +13 -4
  127. package/dist/universal/.opencode/skills/citable/VERSION +1 -1
  128. package/dist/universal/.opencode/skills/citable/agents/citable-auditor.md +64 -0
  129. package/dist/universal/.opencode/skills/citable/agents/citable-semantic-reviewer.md +66 -0
  130. package/dist/universal/.opencode/skills/citable/commands/action-findings.md +9 -1
  131. package/dist/universal/.opencode/skills/citable/commands/observe-and-act.md +50 -3
  132. package/dist/universal/.opencode/skills/citable/commands/plan-audit.md +35 -0
  133. package/dist/universal/.opencode/skills/citable/manifest.json +26 -14
  134. package/dist/universal/.opencode/skills/citable/schemas/action-plan.schema.json +13 -0
  135. package/dist/universal/.opencode/skills/citable/schemas/browser-evidence-plan.schema.json +14 -0
  136. package/dist/universal/.opencode/skills/citable/schemas/canonical-index-import.schema.json +30 -0
  137. package/dist/universal/.opencode/skills/citable/schemas/common.defs.json +12 -1
  138. package/dist/universal/.opencode/skills/citable/schemas/corroboration-import.schema.json +48 -0
  139. package/dist/universal/.opencode/skills/citable/schemas/entity-graph.schema.json +75 -0
  140. package/dist/universal/.opencode/skills/citable/schemas/entity.schema.json +13 -0
  141. package/dist/universal/.opencode/skills/citable/schemas/observation.schema.json +1 -1
  142. package/dist/universal/.opencode/skills/citable/schemas/page.schema.json +3 -0
  143. package/dist/universal/.opencode/skills/citable/schemas/regional-network-import.schema.json +55 -0
  144. package/dist/universal/.opencode/skills/citable/schemas/source-identity-chain.schema.json +83 -0
  145. package/dist/universal/.opencode/skills/citable/scripts/README.md +3 -1
  146. package/dist/universal/.pi/agent/skills/citable/SKILL.md +13 -4
  147. package/dist/universal/.pi/agent/skills/citable/VERSION +1 -1
  148. package/dist/universal/.pi/agent/skills/citable/agents/citable-auditor.md +64 -0
  149. package/dist/universal/.pi/agent/skills/citable/agents/citable-semantic-reviewer.md +66 -0
  150. package/dist/universal/.pi/agent/skills/citable/commands/action-findings.md +9 -1
  151. package/dist/universal/.pi/agent/skills/citable/commands/observe-and-act.md +50 -3
  152. package/dist/universal/.pi/agent/skills/citable/commands/plan-audit.md +35 -0
  153. package/dist/universal/.pi/agent/skills/citable/manifest.json +26 -14
  154. package/dist/universal/.pi/agent/skills/citable/schemas/action-plan.schema.json +13 -0
  155. package/dist/universal/.pi/agent/skills/citable/schemas/browser-evidence-plan.schema.json +14 -0
  156. package/dist/universal/.pi/agent/skills/citable/schemas/canonical-index-import.schema.json +30 -0
  157. package/dist/universal/.pi/agent/skills/citable/schemas/common.defs.json +12 -1
  158. package/dist/universal/.pi/agent/skills/citable/schemas/corroboration-import.schema.json +48 -0
  159. package/dist/universal/.pi/agent/skills/citable/schemas/entity-graph.schema.json +75 -0
  160. package/dist/universal/.pi/agent/skills/citable/schemas/entity.schema.json +13 -0
  161. package/dist/universal/.pi/agent/skills/citable/schemas/observation.schema.json +1 -1
  162. package/dist/universal/.pi/agent/skills/citable/schemas/page.schema.json +3 -0
  163. package/dist/universal/.pi/agent/skills/citable/schemas/regional-network-import.schema.json +55 -0
  164. package/dist/universal/.pi/agent/skills/citable/schemas/source-identity-chain.schema.json +83 -0
  165. package/dist/universal/.pi/agent/skills/citable/scripts/README.md +3 -1
  166. package/dist/universal/.qoder/skills/citable/SKILL.md +13 -4
  167. package/dist/universal/.qoder/skills/citable/VERSION +1 -1
  168. package/dist/universal/.qoder/skills/citable/agents/citable-auditor.md +64 -0
  169. package/dist/universal/.qoder/skills/citable/agents/citable-semantic-reviewer.md +66 -0
  170. package/dist/universal/.qoder/skills/citable/commands/action-findings.md +9 -1
  171. package/dist/universal/.qoder/skills/citable/commands/observe-and-act.md +50 -3
  172. package/dist/universal/.qoder/skills/citable/commands/plan-audit.md +35 -0
  173. package/dist/universal/.qoder/skills/citable/manifest.json +26 -14
  174. package/dist/universal/.qoder/skills/citable/schemas/action-plan.schema.json +13 -0
  175. package/dist/universal/.qoder/skills/citable/schemas/browser-evidence-plan.schema.json +14 -0
  176. package/dist/universal/.qoder/skills/citable/schemas/canonical-index-import.schema.json +30 -0
  177. package/dist/universal/.qoder/skills/citable/schemas/common.defs.json +12 -1
  178. package/dist/universal/.qoder/skills/citable/schemas/corroboration-import.schema.json +48 -0
  179. package/dist/universal/.qoder/skills/citable/schemas/entity-graph.schema.json +75 -0
  180. package/dist/universal/.qoder/skills/citable/schemas/entity.schema.json +13 -0
  181. package/dist/universal/.qoder/skills/citable/schemas/observation.schema.json +1 -1
  182. package/dist/universal/.qoder/skills/citable/schemas/page.schema.json +3 -0
  183. package/dist/universal/.qoder/skills/citable/schemas/regional-network-import.schema.json +55 -0
  184. package/dist/universal/.qoder/skills/citable/schemas/source-identity-chain.schema.json +83 -0
  185. package/dist/universal/.qoder/skills/citable/scripts/README.md +3 -1
  186. package/dist/universal/.rovodev/skills/citable/SKILL.md +13 -4
  187. package/dist/universal/.rovodev/skills/citable/VERSION +1 -1
  188. package/dist/universal/.rovodev/skills/citable/agents/citable-auditor.md +64 -0
  189. package/dist/universal/.rovodev/skills/citable/agents/citable-semantic-reviewer.md +66 -0
  190. package/dist/universal/.rovodev/skills/citable/commands/action-findings.md +9 -1
  191. package/dist/universal/.rovodev/skills/citable/commands/observe-and-act.md +50 -3
  192. package/dist/universal/.rovodev/skills/citable/commands/plan-audit.md +35 -0
  193. package/dist/universal/.rovodev/skills/citable/manifest.json +26 -14
  194. package/dist/universal/.rovodev/skills/citable/schemas/action-plan.schema.json +13 -0
  195. package/dist/universal/.rovodev/skills/citable/schemas/browser-evidence-plan.schema.json +14 -0
  196. package/dist/universal/.rovodev/skills/citable/schemas/canonical-index-import.schema.json +30 -0
  197. package/dist/universal/.rovodev/skills/citable/schemas/common.defs.json +12 -1
  198. package/dist/universal/.rovodev/skills/citable/schemas/corroboration-import.schema.json +48 -0
  199. package/dist/universal/.rovodev/skills/citable/schemas/entity-graph.schema.json +75 -0
  200. package/dist/universal/.rovodev/skills/citable/schemas/entity.schema.json +13 -0
  201. package/dist/universal/.rovodev/skills/citable/schemas/observation.schema.json +1 -1
  202. package/dist/universal/.rovodev/skills/citable/schemas/page.schema.json +3 -0
  203. package/dist/universal/.rovodev/skills/citable/schemas/regional-network-import.schema.json +55 -0
  204. package/dist/universal/.rovodev/skills/citable/schemas/source-identity-chain.schema.json +83 -0
  205. package/dist/universal/.rovodev/skills/citable/scripts/README.md +3 -1
  206. package/dist/universal/.trae/skills/citable/SKILL.md +13 -4
  207. package/dist/universal/.trae/skills/citable/VERSION +1 -1
  208. package/dist/universal/.trae/skills/citable/agents/citable-auditor.md +64 -0
  209. package/dist/universal/.trae/skills/citable/agents/citable-semantic-reviewer.md +66 -0
  210. package/dist/universal/.trae/skills/citable/commands/action-findings.md +9 -1
  211. package/dist/universal/.trae/skills/citable/commands/observe-and-act.md +50 -3
  212. package/dist/universal/.trae/skills/citable/commands/plan-audit.md +35 -0
  213. package/dist/universal/.trae/skills/citable/manifest.json +26 -14
  214. package/dist/universal/.trae/skills/citable/schemas/action-plan.schema.json +13 -0
  215. package/dist/universal/.trae/skills/citable/schemas/browser-evidence-plan.schema.json +14 -0
  216. package/dist/universal/.trae/skills/citable/schemas/canonical-index-import.schema.json +30 -0
  217. package/dist/universal/.trae/skills/citable/schemas/common.defs.json +12 -1
  218. package/dist/universal/.trae/skills/citable/schemas/corroboration-import.schema.json +48 -0
  219. package/dist/universal/.trae/skills/citable/schemas/entity-graph.schema.json +75 -0
  220. package/dist/universal/.trae/skills/citable/schemas/entity.schema.json +13 -0
  221. package/dist/universal/.trae/skills/citable/schemas/observation.schema.json +1 -1
  222. package/dist/universal/.trae/skills/citable/schemas/page.schema.json +3 -0
  223. package/dist/universal/.trae/skills/citable/schemas/regional-network-import.schema.json +55 -0
  224. package/dist/universal/.trae/skills/citable/schemas/source-identity-chain.schema.json +83 -0
  225. package/dist/universal/.trae/skills/citable/scripts/README.md +3 -1
  226. package/dist/universal/.trae-cn/skills/citable/SKILL.md +13 -4
  227. package/dist/universal/.trae-cn/skills/citable/VERSION +1 -1
  228. package/dist/universal/.trae-cn/skills/citable/agents/citable-auditor.md +64 -0
  229. package/dist/universal/.trae-cn/skills/citable/agents/citable-semantic-reviewer.md +66 -0
  230. package/dist/universal/.trae-cn/skills/citable/commands/action-findings.md +9 -1
  231. package/dist/universal/.trae-cn/skills/citable/commands/observe-and-act.md +50 -3
  232. package/dist/universal/.trae-cn/skills/citable/commands/plan-audit.md +35 -0
  233. package/dist/universal/.trae-cn/skills/citable/manifest.json +26 -14
  234. package/dist/universal/.trae-cn/skills/citable/schemas/action-plan.schema.json +13 -0
  235. package/dist/universal/.trae-cn/skills/citable/schemas/browser-evidence-plan.schema.json +14 -0
  236. package/dist/universal/.trae-cn/skills/citable/schemas/canonical-index-import.schema.json +30 -0
  237. package/dist/universal/.trae-cn/skills/citable/schemas/common.defs.json +12 -1
  238. package/dist/universal/.trae-cn/skills/citable/schemas/corroboration-import.schema.json +48 -0
  239. package/dist/universal/.trae-cn/skills/citable/schemas/entity-graph.schema.json +75 -0
  240. package/dist/universal/.trae-cn/skills/citable/schemas/entity.schema.json +13 -0
  241. package/dist/universal/.trae-cn/skills/citable/schemas/observation.schema.json +1 -1
  242. package/dist/universal/.trae-cn/skills/citable/schemas/page.schema.json +3 -0
  243. package/dist/universal/.trae-cn/skills/citable/schemas/regional-network-import.schema.json +55 -0
  244. package/dist/universal/.trae-cn/skills/citable/schemas/source-identity-chain.schema.json +83 -0
  245. package/dist/universal/.trae-cn/skills/citable/scripts/README.md +3 -1
  246. package/dist/universal/manifest.json +270 -171
  247. package/package.json +1 -1
  248. package/schemas/action-plan.schema.json +13 -0
  249. package/schemas/browser-evidence-plan.schema.json +14 -0
  250. package/schemas/canonical-index-import.schema.json +30 -0
  251. package/schemas/common.defs.json +12 -1
  252. package/schemas/corroboration-import.schema.json +48 -0
  253. package/schemas/entity-graph.schema.json +75 -0
  254. package/schemas/entity.schema.json +13 -0
  255. package/schemas/observation.schema.json +1 -1
  256. package/schemas/page.schema.json +3 -0
  257. package/schemas/regional-network-import.schema.json +55 -0
  258. package/schemas/source-identity-chain.schema.json +83 -0
  259. package/scripts/build-dist.js +36 -3
  260. package/skill/SKILL.md +13 -4
  261. package/skill/agents/citable-auditor.md +64 -0
  262. package/skill/agents/citable-semantic-reviewer.md +66 -0
  263. package/skill/commands/action-findings.md +9 -1
  264. package/skill/commands/observe-and-act.md +50 -3
  265. package/skill/commands/plan-audit.md +35 -0
  266. package/src/cli/index.js +38 -5
  267. package/src/commands/actionPlan.js +12 -1
  268. package/src/commands/audit.js +4 -0
  269. package/src/commands/demo.js +36 -0
  270. package/src/commands/fixtures/demo/.citable/claims.yaml +27 -0
  271. package/src/commands/fixtures/demo/.citable/competitors.yaml +3 -0
  272. package/src/commands/fixtures/demo/.citable/config.yaml +18 -0
  273. package/src/commands/fixtures/demo/.citable/crawlers.yaml +30 -0
  274. package/src/commands/fixtures/demo/.citable/entities.yaml +28 -0
  275. package/src/commands/fixtures/demo/.citable/evidence.yaml +19 -0
  276. package/src/commands/fixtures/demo/.citable/experiments.yaml +3 -0
  277. package/src/commands/fixtures/demo/.citable/pages.yaml +54 -0
  278. package/src/commands/fixtures/demo/.citable/prompts.yaml +11 -0
  279. package/src/commands/fixtures/demo/.citable/queries.yaml +10 -0
  280. package/src/commands/fixtures/demo/site/index.html +12 -0
  281. package/src/commands/fixtures/demo/site/learn/execution-governance/index.html +14 -0
  282. package/src/commands/fixtures/demo/site/pricing/index.html +10 -0
  283. package/src/commands/fixtures/demo/site/products/gatekeeper/index.html +15 -0
  284. package/src/commands/fixtures/demo/site/robots.txt +7 -0
  285. package/src/commands/fixtures/demo/site/sitemap.xml +7 -0
  286. package/src/commands/init.js +5 -2
  287. package/src/commands/observe.js +313 -15
  288. package/src/commands/planAudit.js +151 -0
  289. package/src/commands/schemaCmd.js +5 -1
  290. package/src/commands/substantiate.js +22 -10
  291. package/src/crawler/fetch.js +34 -6
  292. package/src/extractor/page.js +17 -0
  293. package/src/installer/index.js +282 -28
  294. package/src/installer/providers.js +11 -0
  295. package/src/observations/browserJourney.js +15 -7
  296. package/src/observations/claimDiff.js +83 -0
  297. package/src/observations/common.js +15 -0
  298. package/src/observations/entityGraph.js +89 -0
  299. package/src/observations/renderParity.js +49 -0
  300. package/src/observations/sourceIdentity.js +62 -0
  301. package/src/registries/seed.js +95 -0
  302. package/src/registries/seeds/saas-pricing/claims.yaml +18 -0
  303. package/src/registries/seeds/saas-pricing/entities.yaml +20 -0
  304. package/src/registries/seeds/saas-pricing/seed.json +6 -0
  305. package/src/shared/checklist.js +19 -0
@@ -0,0 +1,10 @@
1
+ <!doctype html><html lang="en"><head>
2
+ <meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
3
+ <title>Pricing — Gatekeeper plans and deployment options</title>
4
+ <meta name="description" content="Published pricing for Gatekeeper: per-seat SaaS plans and self-hosted licensing, with what each plan does not include.">
5
+ <link rel="canonical" href="https://example.test/pricing/">
6
+ </head><body>
7
+ <h1>Gatekeeper pricing</h1>
8
+ <p>Gatekeeper SaaS starts at 40 USD per seat per month. Self-hosted licensing is quoted per deployment. The starter plan does not include enterprise audit exports; that limitation applies until the Team plan.</p>
9
+ <p><a href="/products/gatekeeper/">Gatekeeper product details</a> · <a href="/">Example Governance home</a></p>
10
+ </body></html>
@@ -0,0 +1,15 @@
1
+ <!doctype html><html lang="en"><head>
2
+ <meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
3
+ <title>Gatekeeper — runtime authorization for AI agents</title>
4
+ <meta name="description" content="Gatekeeper validates AI agent actions against current policy before execution. SaaS or self-hosted, available globally.">
5
+ <link rel="canonical" href="https://example.test/products/gatekeeper/">
6
+ <script type="application/ld+json">{"@context":"https://schema.org","@type":"SoftwareApplication","@id":"https://example.test/products/gatekeeper/#product","name":"Gatekeeper","url":"https://example.test/products/gatekeeper/","publisher":{"@id":"https://example.test/#organization"}}</script>
7
+ </head><body>
8
+ <h1>Gatekeeper</h1>
9
+ <p>Gatekeeper is a runtime enforcement component that validates whether an AI-initiated action remains admissible before execution. It is designed for platform engineering teams operating autonomous agents in production. It is not for teams seeking a model training framework.</p>
10
+ <h2>Deployment and pricing</h2>
11
+ <p>Gatekeeper is available as SaaS or self-hosted. Pricing is published per seat per month; contact sales for enterprise quotes. Gatekeeper is available in North America and Europe.</p>
12
+ <h2>Limitations</h2>
13
+ <p>Gatekeeper does not support advisory-only deployments for enforcement guarantees, and it does not cover workflows that bypass its integration points. Known limitation: latency budgets under 5 ms are out of scope.</p>
14
+ <p><a href="/learn/execution-governance/">Read the execution governance definition</a> or <a href="/pricing/">compare pricing and deployment options</a>. <a href="/">Example Governance home</a></p>
15
+ </body></html>
@@ -0,0 +1,7 @@
1
+ User-agent: GPTBot
2
+ Disallow: /
3
+
4
+ User-agent: *
5
+ Allow: /
6
+
7
+ Sitemap: https://example.test/sitemap.xml
@@ -0,0 +1,7 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
3
+ <url><loc>https://example.test/</loc><lastmod>2026-07-01</lastmod></url>
4
+ <url><loc>https://example.test/products/gatekeeper/</loc><lastmod>2026-07-01</lastmod></url>
5
+ <url><loc>https://example.test/learn/execution-governance/</loc><lastmod>2026-07-01</lastmod></url>
6
+ <url><loc>https://example.test/pricing/</loc><lastmod>2026-07-01</lastmod></url>
7
+ </urlset>
@@ -3,6 +3,7 @@ import path from 'node:path';
3
3
  import { writeYaml, readJson, nowIso, listFiles } from '../shared/io.js';
4
4
  import { REGISTRY_SPECS, emptyRegistry, contextDir } from '../registries/index.js';
5
5
  import { validateAgainst } from '../shared/schemaValidator.js';
6
+ import { applySeed } from '../registries/seed.js';
6
7
 
7
8
  const DEFAULT_CRAWLERS = [
8
9
  ['CRAWLER-GOOGLEBOT', 'Googlebot', 'Google', 'search_indexing'],
@@ -68,7 +69,7 @@ function detectFramework(root) {
68
69
  }
69
70
 
70
71
  /** `citable init` — inspect the repo and create .citable/ without overwriting anything user-maintained. */
71
- export function init(root, { force = false } = {}) {
72
+ export function init(root, { force = false, seed = null } = {}) {
72
73
  const dir = contextDir(root);
73
74
  const created = [];
74
75
  const skipped = [];
@@ -120,5 +121,7 @@ export function init(root, { force = false } = {}) {
120
121
  writeYaml(file, reg);
121
122
  created.push(spec.file);
122
123
  }
123
- return { created, skipped, detected, unresolved };
124
+
125
+ const seedResult = seed ? applySeed(root, seed, { force }) : null;
126
+ return { created, skipped, detected, unresolved, seed: seedResult };
124
127
  }
@@ -1,7 +1,8 @@
1
1
  import { buildContext } from './context.js';
2
2
  import { envelope, observationRun, readInput } from '../observations/common.js';
3
- import { sha256 } from '../shared/io.js';
4
- import { fetchUrl } from '../crawler/fetch.js';
3
+ import { buildClaimDiffContext, diffAssertion } from '../observations/claimDiff.js';
4
+ import { readYaml, sha256 } from '../shared/io.js';
5
+ import { fetchUrl, handlePublicBrowserRoute, validatePublicUrl } from '../crawler/fetch.js';
5
6
  import fs from 'node:fs';
6
7
  import path from 'node:path';
7
8
  import { parse as parseCsv } from 'csv-parse/sync';
@@ -28,6 +29,20 @@ function canonicalReview(raw, targetOrigin) {
28
29
  const url = typeof citation === 'string' ? citation : citation.url;
29
30
  const canonical = typeof citation === 'object' ? citation.canonical_url || url : url;
30
31
  const supports = typeof citation === 'object' ? citation.support_status || 'review_required' : 'review_required';
32
+ const sourceProfile = typeof citation === 'object' ? citation.source_profile || null : null;
33
+ if (sourceProfile) {
34
+ const formats = new Set(['article', 'documentation', 'product', 'landing_page', 'comparison', 'forum', 'video', 'pdf', 'other']);
35
+ const allowedKeys = new Set(['content_format', 'word_count', 'heading_count', 'freshness_date', 'evidence_features']);
36
+ if (Object.keys(sourceProfile).some((key) => !allowedKeys.has(key))
37
+ || !formats.has(sourceProfile.content_format)
38
+ || !Number.isInteger(sourceProfile.word_count) || sourceProfile.word_count < 0
39
+ || !Number.isInteger(sourceProfile.heading_count) || sourceProfile.heading_count < 0
40
+ || !strictDate(sourceProfile.freshness_date)
41
+ || !Array.isArray(sourceProfile.evidence_features)
42
+ || sourceProfile.evidence_features.some((item) => typeof item !== 'string' || !item.trim())) {
43
+ throw new Error(`citation ${index + 1} source_profile violates the declared competitive profile contract`);
44
+ }
45
+ }
31
46
  return {
32
47
  citation_url: url, canonical_url: canonical, citation_order: index + 1,
33
48
  first_party: Boolean(targetOrigin && originOf(canonical) === targetOrigin),
@@ -35,6 +50,7 @@ function canonicalReview(raw, targetOrigin) {
35
50
  answer_claim: typeof citation === 'object' ? citation.answer_claim || null : null,
36
51
  source_passage: typeof citation === 'object' ? citation.source_passage || null : null,
37
52
  reviewer: typeof citation === 'object' ? citation.reviewer || null : null,
53
+ source_profile: sourceProfile,
38
54
  };
39
55
  });
40
56
  }
@@ -42,6 +58,7 @@ function canonicalReview(raw, targetOrigin) {
42
58
  async function observeRender(root, options) {
43
59
  if (options.input) return observeBrowserPlan(root, options);
44
60
  if (!options.target || !/^https?:\/\//.test(options.target)) throw new Error('render requires --target <http(s) URL>');
61
+ await validatePublicUrl(options.target, options.lookup ? { lookup: options.lookup } : {});
45
62
  const profileNames = ['desktop', 'mobile', 'javascript_disabled'];
46
63
  let reused = [], previousRaw = null;
47
64
  if (options.resumeRun) {
@@ -66,6 +83,7 @@ async function observeRender(root, options) {
66
83
  capture = async (name, viewport, { isMobile = false, javaScriptEnabled = true } = {}) => {
67
84
  const context = await browser.newContext({ viewport, isMobile, javaScriptEnabled });
68
85
  try {
86
+ await context.route('**/*', (route) => handlePublicBrowserRoute(route, options.lookup ? { lookup: options.lookup } : {}));
69
87
  const page = await context.newPage();
70
88
  const failures = [];
71
89
  page.on('requestfailed', (request) => failures.push({ url: request.url(), error: request.failure()?.errorText || 'unknown' }));
@@ -130,11 +148,12 @@ async function observeCitations(root, options) {
130
148
  if (options.endpoint) {
131
149
  const prompts = input.value.prompts || (Array.isArray(input.value) ? input.value : []);
132
150
  if (!prompts.length) throw new Error('citation runner input must contain a prompts array');
133
- if (!/^https:\/\//.test(options.endpoint) && !/^http:\/\/(127\.0\.0\.1|localhost)(:\d+)?\//.test(options.endpoint)) throw new Error('citation adapter endpoint must use HTTPS (loopback HTTP is allowed for testing)');
151
+ if (!/^https:\/\//.test(options.endpoint)) throw new Error('citation adapter endpoint must use HTTPS');
152
+ await validatePublicUrl(options.endpoint, options.lookup ? { lookup: options.lookup } : {});
134
153
  const repeat = Number.isInteger(options.repeat) && options.repeat > 0 && options.repeat <= 20 ? options.repeat : 3;
135
154
  rows = [];
136
155
  for (const prompt of prompts) for (let runIndex = 1; runIndex <= repeat; runIndex++) {
137
- const response = await fetch(options.endpoint, { method: 'POST', headers: { 'content-type': 'application/json', ...(options.accessToken ? { authorization: `Bearer ${options.accessToken}` } : {}) }, body: JSON.stringify({ prompt_id: prompt.prompt_id, prompt_text: prompt.prompt_text, locale: prompt.locale || input.value.locale || 'en-US', run_index: runIndex, runs_in_series: repeat }) });
156
+ const response = await (options.adapterFetch || globalThis.fetch)(options.endpoint, { method: 'POST', redirect: 'error', headers: { 'content-type': 'application/json', ...(options.accessToken ? { authorization: `Bearer ${options.accessToken}` } : {}) }, body: JSON.stringify({ prompt_id: prompt.prompt_id, prompt_text: prompt.prompt_text, locale: prompt.locale || input.value.locale || 'en-US', run_index: runIndex, runs_in_series: repeat }) });
138
157
  const rawResponse = await response.text();
139
158
  if (!response.ok) throw new Error(`citation adapter returned ${response.status}: ${rawResponse.slice(0, 300)}`);
140
159
  const result = JSON.parse(rawResponse);
@@ -143,14 +162,23 @@ async function observeCitations(root, options) {
143
162
  method = 'live_api';
144
163
  }
145
164
  const targetOrigin = options.target ? originOf(options.target) : input.value.target_origin || null;
165
+ // Registries-only context (no `target` passed — this never fetches or crawls); degrades
166
+ // gracefully to empty registries (and therefore all-`unsupported` diffs) when no
167
+ // `.citable/` exists yet, per loadRegistries()'s missing-file-loads-as-empty contract.
168
+ const diffCtx = buildClaimDiffContext(root, options.refDate);
146
169
  const observations = [];
147
170
  for (const row of rows) {
148
- const reviews = canonicalReview(row, targetOrigin);
171
+ const reviews = canonicalReview(row, targetOrigin).map((review) => ({
172
+ ...review,
173
+ claim_diff: diffAssertion(review.answer_claim, review.canonical_url, diffCtx),
174
+ }));
175
+ const diffCounts = { confirmed: 0, contradicted: 0, stale: 0, unsupported: 0, not_checked: 0 };
176
+ for (const review of reviews) diffCounts[review.claim_diff?.status ?? 'not_checked']++;
149
177
  observations.push(envelope('citation', {
150
178
  prompt_id: row.prompt_id, prompt_text: row.prompt_text, provider: row.provider || row.engine,
151
179
  product_mode: row.product_mode || row.interface || 'unknown', locale: row.locale || 'unknown',
152
180
  answer_text: row.answer_text || '', citations: reviews, run_index: row.run_index || 1,
153
- property_cited: reviews.some((r) => r.first_party),
181
+ property_cited: reviews.some((r) => r.first_party), claim_diff_summary: diffCounts,
154
182
  }, { method, source: options.endpoint || input.file, raw: JSON.stringify(row) }));
155
183
  for (const review of reviews) observations.push(envelope('citation_review', review, {
156
184
  method: review.reviewer ? 'human_review' : 'static_analysis', source: options.endpoint || input.file,
@@ -159,7 +187,8 @@ async function observeCitations(root, options) {
159
187
  limitations: review.reviewer ? [] : ['Material support requires a named human reviewer.'],
160
188
  }));
161
189
  }
162
- return observationRun(root, 'observe citations', input.file, observations, { rawInputs: { citation_results: input.raw } });
190
+ const claimDiffWarnings = diffCtx.problems.map((p) => `claim-diff registry problem (diff degraded to unsupported where affected): ${p}`);
191
+ return observationRun(root, 'observe citations', input.file, observations, { rawInputs: { citation_results: input.raw }, warnings: claimDiffWarnings });
163
192
  }
164
193
 
165
194
  function observeLogs(root, options) {
@@ -248,24 +277,82 @@ async function observePassages(root, options) {
248
277
  const ctx = await buildContext(root, options);
249
278
  if (!ctx.site) throw new Error('passages requires --target <dir|url>');
250
279
  const observations = [];
280
+ const regionPages = new Map();
281
+ for (const page of ctx.site.pages) {
282
+ for (const region of page.structuralRegions || []) {
283
+ const key = sha256(`${region.region_type}\0${region.text}`);
284
+ if (!regionPages.has(key)) regionPages.set(key, new Set());
285
+ regionPages.get(key).add(page.url);
286
+ }
287
+ }
251
288
  for (const page of ctx.site.pages) {
252
289
  const rawWords = words(page.text).length;
290
+ const pageRecord = ctx.registries.pages?.entries?.find((item) => {
291
+ try { return new URL(item.url, page.url).href === page.url; } catch { return item.url === page.url; }
292
+ });
293
+ const promptMap = new Map((ctx.registries.prompts?.entries || []).map((item) => [item.prompt_id, item]));
294
+ const questionContexts = (pageRecord?.target_prompts || []).map((id) => promptMap.get(id)).filter(Boolean).map((prompt) => ({
295
+ prompt_id: prompt.prompt_id,
296
+ question: prompt.canonical_prompt,
297
+ expected_answer_components: prompt.expected_answer_components || [],
298
+ }));
299
+ const extractionMetrics = {
300
+ raw_bytes: Buffer.byteLength(page.rawHtml),
301
+ extracted_bytes: Buffer.byteLength(page.text),
302
+ raw_visible_tokens: page.rawVisibleWordCount,
303
+ extracted_tokens: rawWords,
304
+ extracted_to_raw_byte_ratio: page.rawHtml.length ? Number((Buffer.byteLength(page.text) / Buffer.byteLength(page.rawHtml)).toFixed(3)) : null,
305
+ extracted_to_raw_visible_token_ratio: page.rawVisibleWordCount ? Number((rawWords / page.rawVisibleWordCount).toFixed(3)) : null,
306
+ };
307
+ const repeatedRegions = (page.structuralRegions || []).map((region) => {
308
+ const regionHash = sha256(`${region.region_type}\0${region.text}`);
309
+ const pageOccurrences = regionPages.get(regionHash)?.size || 1;
310
+ return {
311
+ region_type: region.region_type,
312
+ region_hash: regionHash,
313
+ html_bytes: region.html_bytes,
314
+ token_count: region.token_count,
315
+ page_occurrences: pageOccurrences,
316
+ classification: pageOccurrences >= 2 ? 'repeated_site_region' : 'page_local_region',
317
+ };
318
+ });
319
+ const limitations = ['Repeated structural regions and extraction ratios are descriptive cohort evidence; they do not establish content harm, retrieval impact, or ranking effect.'];
253
320
  for (let i = 0; i < page.paragraphs.length; i++) {
254
321
  const passage = page.paragraphs.slice(i, i + 3).join(' ');
255
322
  const count = words(passage).length;
256
323
  if (count < 30) continue;
257
324
  const dependencies = [];
258
325
  if (/\b(this|that|these|those|it|they|above|below|here)\b/i.test(passage.slice(0, 100))) dependencies.push('possible external referent');
259
- observations.push(envelope('passage', { url: page.url, passage_index: i, text: passage, word_count: count, independently_extractable: count >= 100 && count <= 300 && dependencies.length === 0, dependencies, content_to_noise_ratio: page.rawHtml.length ? Number((page.text.length / page.rawHtml.length).toFixed(3)) : null }, { method: 'static_analysis', source: page.sourceFile || page.url, raw: passage, confidence: 'medium' }));
326
+ observations.push(envelope('passage', { url: page.url, passage_contract_version: 1, passage_index: i, text: passage, word_count: count, independently_extractable: count >= 100 && count <= 300 && dependencies.length === 0, dependencies, question_contexts: questionContexts, semantic_review_status: 'review_required', content_to_noise_ratio: extractionMetrics.extracted_to_raw_byte_ratio, extraction_metrics: extractionMetrics, repeated_regions: repeatedRegions }, { method: 'static_analysis', source: page.sourceFile || page.url, raw: passage, confidence: 'medium', limitations: [...limitations, 'Question-to-passage suitability remains review_required and is not inferred from registry linkage.'] }));
260
327
  }
261
- if (!page.paragraphs.length) observations.push(envelope('passage', { url: page.url, word_count: rawWords, independently_extractable: false, dependencies: ['no paragraph passages extracted'] }, { method: 'static_analysis', source: page.sourceFile || page.url, state: 'not_observed', confidence: 'confirmed' }));
328
+ if (!page.paragraphs.length) observations.push(envelope('passage', { url: page.url, passage_contract_version: 1, word_count: rawWords, independently_extractable: false, dependencies: ['no paragraph passages extracted'], question_contexts: questionContexts, semantic_review_status: 'review_required', extraction_metrics: extractionMetrics, repeated_regions: repeatedRegions }, { method: 'static_analysis', source: page.sourceFile || page.url, state: 'not_observed', confidence: 'confirmed', limitations: [...limitations, 'Question-to-passage suitability remains review_required and is not inferred from registry linkage.'] }));
262
329
  }
263
330
  return observationRun(root, 'observe passages', options.target, observations);
264
331
  }
265
332
 
333
+ function normalizeDateSignal(source, raw) {
334
+ if (raw == null || raw === '') return null;
335
+ const text = String(raw).trim();
336
+ const dateOnly = /^\d{4}-\d{2}-\d{2}$/.test(text);
337
+ const date = new Date(dateOnly ? `${text}T00:00:00Z` : text);
338
+ const valid = !Number.isNaN(date.getTime()) && (!dateOnly || date.toISOString().slice(0, 10) === text);
339
+ return {
340
+ source,
341
+ raw: text,
342
+ normalized_date: valid ? date.toISOString().slice(0, 10) : null,
343
+ valid,
344
+ };
345
+ }
346
+
266
347
  async function observeConsensus(root, options) {
267
348
  const ctx = await buildContext(root, options);
268
349
  if (!ctx.site) throw new Error('consensus requires --target <dir|url>');
350
+ let canonicalInput = null;
351
+ if (options.input) {
352
+ canonicalInput = readInput(options.input);
353
+ const check = validateAgainst('canonical-index-import.schema.json', canonicalInput.value);
354
+ if (!check.valid) throw new Error(`canonical index import violates contract: ${check.errors.join('; ')}`);
355
+ }
269
356
  const sitemapRows = ctx.site.sitemaps.flatMap((s) => s.parsed?.urls || []);
270
357
  const sitemapUrls = new Set(sitemapRows.map((u) => u.loc));
271
358
  const observations = ctx.site.pages.map((page) => {
@@ -274,10 +361,59 @@ async function observeConsensus(root, options) {
274
361
  const visibleDate = page.metas['article:modified_time']?.[0] || page.metas['date.modified']?.[0] || page.metas['last-modified']?.[0] || null;
275
362
  const signals = { final_url: page.url, html_canonical: declared, open_graph_url: page.ogUrl, sitemap_present: sitemapUrls.has(page.url), last_modified_header: page.headers['last-modified'] || null, sitemap_lastmod: sitemapDate, visible_or_meta_modified: visibleDate };
276
363
  const urls = [page.url, declared, page.ogUrl].filter(Boolean);
277
- const dates = [signals.last_modified_header, sitemapDate, visibleDate].filter(Boolean).map((value) => String(value).slice(0, 10));
278
- return envelope('canonical_freshness', { url: page.url, signals, canonical_consensus: new Set(urls).size <= 1, date_consensus: dates.length >= 2 ? new Set(dates).size === 1 : null, engine_selected_canonical: null }, { method: 'static_analysis', source: page.sourceFile || page.url, raw: JSON.stringify(signals), confidence: 'high', limitations: ['Engine-selected canonical and content-difference date require external observations.'] });
364
+ const engineCanonicals = canonicalInput?.value.observations.filter((item) => item.url === page.url).map((item) => ({
365
+ engine: item.engine,
366
+ selected_canonical: item.selected_canonical,
367
+ indexed: item.indexed,
368
+ observed_at: item.observed_at,
369
+ collection_authority: canonicalInput.value.collection_authority,
370
+ authenticity_status: canonicalInput.value.authenticity_status,
371
+ })) || [];
372
+ const dateSignals = [
373
+ normalizeDateSignal('http_last_modified', signals.last_modified_header),
374
+ normalizeDateSignal('sitemap_lastmod', sitemapDate),
375
+ normalizeDateSignal('visible_or_meta_modified', visibleDate),
376
+ ].filter(Boolean);
377
+ const validDates = dateSignals.filter((item) => item.valid).map((item) => item.normalized_date);
378
+ const dateConsensus = validDates.length >= 2 ? new Set(validDates).size === 1 : null;
379
+ const prior = ctx.snapshots?.pages?.[page.url] || null;
380
+ const currentHash = ctx.hashPage(page);
381
+ const changed = prior ? prior.contentHash !== currentHash : null;
382
+ const pageRegistry = ctx.registries.pages?.entries?.find((item) => item.url === page.url || item.canonical_url === page.url) || null;
383
+ const contentSnapshot = {
384
+ current_content_hash: currentHash,
385
+ prior_run_id: prior ? ctx.snapshots.run_id || null : null,
386
+ prior_content_hash: prior?.contentHash || null,
387
+ changed_since_snapshot: changed,
388
+ change_observed_after: changed ? ctx.snapshots.taken_at || null : null,
389
+ change_observed_by: changed ? new Date().toISOString() : null,
390
+ exact_change_time_established: false,
391
+ };
392
+ const limitations = ['Engine-selected canonical requires external observations. Snapshot comparison can bound a detected change interval but cannot establish the exact modification time or cause.'];
393
+ if (dateSignals.some((item) => !item.valid)) limitations.push('One or more declared freshness dates could not be parsed and were excluded from consensus.');
394
+ const data = {
395
+ url: page.url,
396
+ signals,
397
+ canonical_consensus: new Set(urls).size <= 1,
398
+ date_signals: dateSignals,
399
+ date_consensus: dateConsensus,
400
+ freshness_assessment: validDates.length < 2 ? 'insufficient_signals' : dateConsensus ? 'aligned_signals' : 'conflicting_signals',
401
+ content_snapshot: contentSnapshot,
402
+ lifecycle_governance: pageRegistry ? {
403
+ page_id: pageRegistry.page_id,
404
+ lifecycle_class: pageRegistry.lifecycle_class || 'unclassified',
405
+ next_review_date: pageRegistry.next_review_date || null,
406
+ } : null,
407
+ engine_selected_canonical: engineCanonicals.length ? engineCanonicals : null,
408
+ canonical_consensus_with_engines: engineCanonicals.length
409
+ ? new Set([...urls, ...engineCanonicals.map((item) => item.selected_canonical).filter(Boolean)]).size <= 1
410
+ : null,
411
+ };
412
+ return envelope('canonical_freshness', data, { method: 'static_analysis', source: page.sourceFile || page.url, raw: JSON.stringify({ signals, dateSignals, contentSnapshot }), confidence: 'high', limitations });
413
+ });
414
+ return observationRun(root, 'observe consensus', options.target, observations, {
415
+ rawInputs: canonicalInput ? { canonical_index_export: canonicalInput.raw } : {},
279
416
  });
280
- return observationRun(root, 'observe consensus', options.target, observations);
281
417
  }
282
418
 
283
419
  const median = (values) => {
@@ -374,11 +510,171 @@ async function observePerformance(root, options) {
374
510
 
375
511
  function observeCorroboration(root, options) {
376
512
  const input = readInput(options.input);
377
- const rows = Array.isArray(input.value) ? input.value : input.value.mentions || [];
378
- const observations = rows.map((row) => envelope('corroboration', row, { method: 'owner_import', source: input.file, raw: JSON.stringify(row), confidence: row.independent === true ? 'high' : 'low', limitations: row.independent === true ? [] : ['Source independence is not established.'] }));
513
+ const document = input.value;
514
+ const check = validateAgainst('corroboration-import.schema.json', document);
515
+ if (!check.valid) throw new Error(`corroboration import violates contract: ${check.errors.join('; ')}`);
516
+ const observations = document.mentions.map((row) => {
517
+ const independent = row.source_control === 'independent' && row.relationship_disclosure === 'none_disclosed';
518
+ const sourceAuthority = independent
519
+ ? 'independently_controlled'
520
+ : row.source_control === 'owner_controlled'
521
+ ? 'owner_controlled'
522
+ : row.source_control === 'affiliated'
523
+ ? 'third_party_commercial'
524
+ : 'unknown';
525
+ const limitations = [
526
+ 'An observed external mention does not establish source authority, claim support, ranking impact, citation eligibility, or representative coverage.',
527
+ ];
528
+ if (!independent) limitations.push('Source independence is not established from the declared control and relationship.');
529
+ const data = {
530
+ ...row,
531
+ import_provider: document.provider,
532
+ import_dataset: document.dataset,
533
+ import_collected_at: document.collected_at,
534
+ coverage: document.coverage,
535
+ };
536
+ return envelope('corroboration', data, {
537
+ method: 'owner_import',
538
+ source: row.source_url,
539
+ raw: JSON.stringify(row),
540
+ confidence: independent ? 'high' : 'low',
541
+ limitations,
542
+ authority: {
543
+ source_authority: sourceAuthority,
544
+ collection_authority: document.collection_authority,
545
+ authenticity_status: document.authenticity_status,
546
+ representativeness: document.representativeness,
547
+ },
548
+ });
549
+ });
379
550
  return observationRun(root, 'observe corroboration', input.file, observations, { rawInputs: { corroboration_export: input.raw } });
380
551
  }
381
552
 
553
+ const PROBE_HEADER_ALLOWLIST = new Set([
554
+ 'age', 'cache-control', 'cf-cache-status', 'cf-mitigated', 'content-type',
555
+ 'date', 'etag', 'retry-after', 'server', 'via', 'x-cache', 'x-robots-tag',
556
+ ]);
557
+
558
+ function safeProbeHeaders(headers = {}) {
559
+ return Object.fromEntries(Object.entries(headers)
560
+ .map(([key, value]) => [key.toLowerCase(), String(value)])
561
+ .filter(([key]) => PROBE_HEADER_ALLOWLIST.has(key)));
562
+ }
563
+
564
+ function classifyProbeResponse(response) {
565
+ const headers = safeProbeHeaders(response.headers);
566
+ const body = String(response.body || '').slice(0, 10000);
567
+ const challengeSignal = headers['cf-mitigated'] === 'challenge'
568
+ || /captcha|verify you are human|attention required|access challenge/i.test(body);
569
+ if ([401, 403, 429].includes(response.status) || challengeSignal) return 'challenged';
570
+ if (response.status >= 500) return 'server_error';
571
+ if (response.status >= 400) return 'blocked';
572
+ if ((response.redirectChain || []).length) return 'redirected';
573
+ return response.status >= 200 && response.status < 400 ? 'accessible' : 'unexpected';
574
+ }
575
+
576
+ async function observeCrawlerProbes(root, options) {
577
+ if (!options.target) throw new Error('probes requires --target <public URL>');
578
+ const registryFile = path.join(root, '.citable', 'crawlers.yaml');
579
+ if (!fs.existsSync(registryFile)) throw new Error('crawlers registry is missing; run citable init first');
580
+ const rawRegistry = fs.readFileSync(registryFile, 'utf8');
581
+ const registry = readYaml(registryFile);
582
+ const check = validateAgainst('crawler.schema.json', registry);
583
+ if (!check.valid) throw new Error(`crawlers registry violates contract: ${check.errors.join('; ')}`);
584
+ const crawlers = registry.entries.filter((item) => item.status === 'active');
585
+ if (!crawlers.length) throw new Error('crawlers registry has no active identities to probe');
586
+ await validatePublicUrl(options.target, options.lookup ? { lookup: options.lookup } : {});
587
+
588
+ const collector = options.fetchUrl || fetchUrl;
589
+ const region = options.region || 'local_unspecified';
590
+ const observations = [], incomplete = [], artifacts = {};
591
+ for (const crawler of crawlers) {
592
+ const artifactBase = `crawler-probes/${crawler.crawler_id}`;
593
+ try {
594
+ const response = await collector(options.target, {
595
+ userAgent: crawler.user_agent,
596
+ maxRetries: 1,
597
+ ...(options.lookup ? { lookup: options.lookup } : {}),
598
+ });
599
+ const headers = safeProbeHeaders(response.headers);
600
+ const edgeClassification = classifyProbeResponse(response);
601
+ const data = {
602
+ crawler_id: crawler.crawler_id,
603
+ vendor: crawler.vendor || null,
604
+ purpose: crawler.purpose,
605
+ declared_policy_decision: crawler.decision,
606
+ user_agent: crawler.user_agent,
607
+ target: options.target,
608
+ final_url: response.url || options.target,
609
+ status: response.status,
610
+ redirect_chain: response.redirectChain || [],
611
+ response_headers: headers,
612
+ response_hash: sha256(response.body || ''),
613
+ edge_classification: edgeClassification,
614
+ region,
615
+ identity_status: 'synthetically_observed',
616
+ production_access_established: false,
617
+ };
618
+ observations.push(envelope('crawler_probe', data, {
619
+ method: 'synthetic_fetch',
620
+ source: options.target,
621
+ raw: response.body || JSON.stringify(data),
622
+ confidence: 'confirmed',
623
+ limitations: ['A spoofed user agent in a synthetic request does not establish crawler identity or production crawler access. One local probe does not represent other regions, networks, times, or edge paths.'],
624
+ authority: { source_authority: 'synthetic', collection_authority: 'synthetic_probe', authenticity_status: 'unverified', representativeness: 'single_observation' },
625
+ }));
626
+ artifacts[`${artifactBase}/response.txt`] = response.body || '';
627
+ artifacts[`${artifactBase}/headers.json`] = headers;
628
+ } catch (error) {
629
+ incomplete.push(`${crawler.crawler_id} synthetic probe failed: ${error.message}`);
630
+ observations.push(envelope('crawler_probe', {
631
+ crawler_id: crawler.crawler_id, vendor: crawler.vendor || null, purpose: crawler.purpose,
632
+ declared_policy_decision: crawler.decision, user_agent: crawler.user_agent,
633
+ target: options.target, region, edge_classification: 'error',
634
+ identity_status: 'synthetically_observed', production_access_established: false,
635
+ }, {
636
+ method: 'synthetic_fetch', source: options.target, state: 'failed',
637
+ confidence: 'confirmed', raw: `${crawler.crawler_id}:${error.message}`,
638
+ limitations: [`Synthetic probe failed: ${error.message}`, 'A spoofed user agent does not establish crawler identity or production crawler access.'],
639
+ authority: { source_authority: 'synthetic', collection_authority: 'synthetic_probe', authenticity_status: 'unverified', representativeness: 'single_observation' },
640
+ }));
641
+ }
642
+ }
643
+ return observationRun(root, 'observe probes', options.target, observations, {
644
+ rawInputs: { crawler_registry: rawRegistry }, incomplete, artifacts,
645
+ warnings: ['Probe outcomes are synthetic retrieval evidence only and must not be interpreted as verified crawler identity or production access.'],
646
+ });
647
+ }
648
+
649
+ function observeRegionalNetwork(root, options) {
650
+ const input = readInput(options.input);
651
+ const document = input.value;
652
+ const check = validateAgainst('regional-network-import.schema.json', document);
653
+ if (!check.valid) throw new Error(`regional network import violates contract: ${check.errors.join('; ')}`);
654
+ const limitations = ['Imported regional observations do not establish managed runner coverage, global representativeness, crawler identity, or future availability.'];
655
+ const observations = document.probes.map((probe) => envelope('regional_network', {
656
+ ...probe,
657
+ provider: document.provider,
658
+ dataset_collected_at: document.collected_at,
659
+ }, {
660
+ method: 'owner_import',
661
+ source: input.file,
662
+ raw: JSON.stringify(probe),
663
+ confidence: 'confirmed',
664
+ limitations,
665
+ authority: {
666
+ source_authority: 'unknown',
667
+ collection_authority: document.collection_authority,
668
+ authenticity_status: document.authenticity_status,
669
+ representativeness: document.representativeness,
670
+ },
671
+ }));
672
+ return observationRun(root, 'observe network', input.file, observations, {
673
+ rawInputs: { regional_network_export: input.raw },
674
+ warnings: ['This release validates regional probe exports but does not operate a managed multi-region runner.'],
675
+ });
676
+ }
677
+
382
678
  async function observeRepresentation(root, options) {
383
679
  const input = readInput(options.input);
384
680
  const manifest = input.value;
@@ -461,8 +757,10 @@ export async function observe(root, mode, options = {}) {
461
757
  case 'consensus': return observeConsensus(root, options);
462
758
  case 'performance': return observePerformance(root, options);
463
759
  case 'corroboration': return observeCorroboration(root, options);
760
+ case 'probes': return observeCrawlerProbes(root, options);
761
+ case 'network': return observeRegionalNetwork(root, options);
464
762
  case 'media': return observeMedia(root, options);
465
763
  case 'representation': return observeRepresentation(root, options);
466
- default: throw new Error('observe mode must be render, index, citations, logs, bing, passages, consensus, performance, corroboration, media, or representation');
764
+ default: throw new Error('observe mode must be render, index, citations, logs, bing, passages, consensus, performance, corroboration, probes, network, media, or representation');
467
765
  }
468
766
  }