@nebulacomponents/citable 1.19.0 → 1.21.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.
- package/CHANGELOG.md +35 -0
- package/README.md +12 -1
- package/dist/universal/.agents/skills/citable/SKILL.md +11 -2
- package/dist/universal/.agents/skills/citable/VERSION +1 -1
- package/dist/universal/.agents/skills/citable/manifest.json +24 -12
- package/dist/universal/.agents/skills/citable/schemas/action-plan.schema.json +38 -1
- package/dist/universal/.agents/skills/citable/schemas/audit-coverage.schema.json +118 -0
- package/dist/universal/.agents/skills/citable/schemas/claim-evidence.schema.json +21 -0
- package/dist/universal/.agents/skills/citable/schemas/claim-verification.schema.json +18 -0
- package/dist/universal/.agents/skills/citable/schemas/collection-result.schema.json +35 -0
- package/dist/universal/.agents/skills/citable/schemas/config.schema.json +2 -0
- package/dist/universal/.agents/skills/citable/schemas/cro-report.schema.json +8 -1
- package/dist/universal/.agents/skills/citable/schemas/derivation.schema.json +18 -0
- package/dist/universal/.agents/skills/citable/schemas/external-comparison.schema.json +16 -0
- package/dist/universal/.agents/skills/citable/schemas/external-observation.schema.json +32 -0
- package/dist/universal/.agents/skills/citable/schemas/finding.schema.json +105 -2
- package/dist/universal/.agents/skills/citable/schemas/prompt-set.schema.json +47 -0
- package/dist/universal/.agents/skills/citable/schemas/remediation-verification.schema.json +12 -1
- package/dist/universal/.agents/skills/citable/schemas/representation-data.schema.json +21 -0
- package/dist/universal/.agents/skills/citable/schemas/research-dataset.schema.json +27 -0
- package/dist/universal/.agents/skills/citable/schemas/run.schema.json +15 -2
- package/dist/universal/.agents/skills/citable/schemas/search-discovery-data.schema.json +20 -0
- package/dist/universal/.agents/skills/citable/schemas/search-report.schema.json +8 -1
- package/dist/universal/.agents/skills/citable/schemas/summary.schema.json +41 -0
- package/dist/universal/.claude/agents/citable/manifest.json +2 -2
- package/dist/universal/.claude/skills/citable/SKILL.md +11 -2
- package/dist/universal/.claude/skills/citable/VERSION +1 -1
- package/dist/universal/.claude/skills/citable/manifest.json +24 -12
- package/dist/universal/.claude/skills/citable/schemas/action-plan.schema.json +38 -1
- package/dist/universal/.claude/skills/citable/schemas/audit-coverage.schema.json +118 -0
- package/dist/universal/.claude/skills/citable/schemas/claim-evidence.schema.json +21 -0
- package/dist/universal/.claude/skills/citable/schemas/claim-verification.schema.json +18 -0
- package/dist/universal/.claude/skills/citable/schemas/collection-result.schema.json +35 -0
- package/dist/universal/.claude/skills/citable/schemas/config.schema.json +2 -0
- package/dist/universal/.claude/skills/citable/schemas/cro-report.schema.json +8 -1
- package/dist/universal/.claude/skills/citable/schemas/derivation.schema.json +18 -0
- package/dist/universal/.claude/skills/citable/schemas/external-comparison.schema.json +16 -0
- package/dist/universal/.claude/skills/citable/schemas/external-observation.schema.json +32 -0
- package/dist/universal/.claude/skills/citable/schemas/finding.schema.json +105 -2
- package/dist/universal/.claude/skills/citable/schemas/prompt-set.schema.json +47 -0
- package/dist/universal/.claude/skills/citable/schemas/remediation-verification.schema.json +12 -1
- package/dist/universal/.claude/skills/citable/schemas/representation-data.schema.json +21 -0
- package/dist/universal/.claude/skills/citable/schemas/research-dataset.schema.json +27 -0
- package/dist/universal/.claude/skills/citable/schemas/run.schema.json +15 -2
- package/dist/universal/.claude/skills/citable/schemas/search-discovery-data.schema.json +20 -0
- package/dist/universal/.claude/skills/citable/schemas/search-report.schema.json +8 -1
- package/dist/universal/.claude/skills/citable/schemas/summary.schema.json +41 -0
- package/dist/universal/.cursor/skills/citable/SKILL.md +11 -2
- package/dist/universal/.cursor/skills/citable/VERSION +1 -1
- package/dist/universal/.cursor/skills/citable/manifest.json +24 -12
- package/dist/universal/.cursor/skills/citable/schemas/action-plan.schema.json +38 -1
- package/dist/universal/.cursor/skills/citable/schemas/audit-coverage.schema.json +118 -0
- package/dist/universal/.cursor/skills/citable/schemas/claim-evidence.schema.json +21 -0
- package/dist/universal/.cursor/skills/citable/schemas/claim-verification.schema.json +18 -0
- package/dist/universal/.cursor/skills/citable/schemas/collection-result.schema.json +35 -0
- package/dist/universal/.cursor/skills/citable/schemas/config.schema.json +2 -0
- package/dist/universal/.cursor/skills/citable/schemas/cro-report.schema.json +8 -1
- package/dist/universal/.cursor/skills/citable/schemas/derivation.schema.json +18 -0
- package/dist/universal/.cursor/skills/citable/schemas/external-comparison.schema.json +16 -0
- package/dist/universal/.cursor/skills/citable/schemas/external-observation.schema.json +32 -0
- package/dist/universal/.cursor/skills/citable/schemas/finding.schema.json +105 -2
- package/dist/universal/.cursor/skills/citable/schemas/prompt-set.schema.json +47 -0
- package/dist/universal/.cursor/skills/citable/schemas/remediation-verification.schema.json +12 -1
- package/dist/universal/.cursor/skills/citable/schemas/representation-data.schema.json +21 -0
- package/dist/universal/.cursor/skills/citable/schemas/research-dataset.schema.json +27 -0
- package/dist/universal/.cursor/skills/citable/schemas/run.schema.json +15 -2
- package/dist/universal/.cursor/skills/citable/schemas/search-discovery-data.schema.json +20 -0
- package/dist/universal/.cursor/skills/citable/schemas/search-report.schema.json +8 -1
- package/dist/universal/.cursor/skills/citable/schemas/summary.schema.json +41 -0
- package/dist/universal/.gemini/skills/citable/SKILL.md +11 -2
- package/dist/universal/.gemini/skills/citable/VERSION +1 -1
- package/dist/universal/.gemini/skills/citable/manifest.json +24 -12
- package/dist/universal/.gemini/skills/citable/schemas/action-plan.schema.json +38 -1
- package/dist/universal/.gemini/skills/citable/schemas/audit-coverage.schema.json +118 -0
- package/dist/universal/.gemini/skills/citable/schemas/claim-evidence.schema.json +21 -0
- package/dist/universal/.gemini/skills/citable/schemas/claim-verification.schema.json +18 -0
- package/dist/universal/.gemini/skills/citable/schemas/collection-result.schema.json +35 -0
- package/dist/universal/.gemini/skills/citable/schemas/config.schema.json +2 -0
- package/dist/universal/.gemini/skills/citable/schemas/cro-report.schema.json +8 -1
- package/dist/universal/.gemini/skills/citable/schemas/derivation.schema.json +18 -0
- package/dist/universal/.gemini/skills/citable/schemas/external-comparison.schema.json +16 -0
- package/dist/universal/.gemini/skills/citable/schemas/external-observation.schema.json +32 -0
- package/dist/universal/.gemini/skills/citable/schemas/finding.schema.json +105 -2
- package/dist/universal/.gemini/skills/citable/schemas/prompt-set.schema.json +47 -0
- package/dist/universal/.gemini/skills/citable/schemas/remediation-verification.schema.json +12 -1
- package/dist/universal/.gemini/skills/citable/schemas/representation-data.schema.json +21 -0
- package/dist/universal/.gemini/skills/citable/schemas/research-dataset.schema.json +27 -0
- package/dist/universal/.gemini/skills/citable/schemas/run.schema.json +15 -2
- package/dist/universal/.gemini/skills/citable/schemas/search-discovery-data.schema.json +20 -0
- package/dist/universal/.gemini/skills/citable/schemas/search-report.schema.json +8 -1
- package/dist/universal/.gemini/skills/citable/schemas/summary.schema.json +41 -0
- package/dist/universal/.github/skills/citable/SKILL.md +11 -2
- package/dist/universal/.github/skills/citable/VERSION +1 -1
- package/dist/universal/.github/skills/citable/manifest.json +24 -12
- package/dist/universal/.github/skills/citable/schemas/action-plan.schema.json +38 -1
- package/dist/universal/.github/skills/citable/schemas/audit-coverage.schema.json +118 -0
- package/dist/universal/.github/skills/citable/schemas/claim-evidence.schema.json +21 -0
- package/dist/universal/.github/skills/citable/schemas/claim-verification.schema.json +18 -0
- package/dist/universal/.github/skills/citable/schemas/collection-result.schema.json +35 -0
- package/dist/universal/.github/skills/citable/schemas/config.schema.json +2 -0
- package/dist/universal/.github/skills/citable/schemas/cro-report.schema.json +8 -1
- package/dist/universal/.github/skills/citable/schemas/derivation.schema.json +18 -0
- package/dist/universal/.github/skills/citable/schemas/external-comparison.schema.json +16 -0
- package/dist/universal/.github/skills/citable/schemas/external-observation.schema.json +32 -0
- package/dist/universal/.github/skills/citable/schemas/finding.schema.json +105 -2
- package/dist/universal/.github/skills/citable/schemas/prompt-set.schema.json +47 -0
- package/dist/universal/.github/skills/citable/schemas/remediation-verification.schema.json +12 -1
- package/dist/universal/.github/skills/citable/schemas/representation-data.schema.json +21 -0
- package/dist/universal/.github/skills/citable/schemas/research-dataset.schema.json +27 -0
- package/dist/universal/.github/skills/citable/schemas/run.schema.json +15 -2
- package/dist/universal/.github/skills/citable/schemas/search-discovery-data.schema.json +20 -0
- package/dist/universal/.github/skills/citable/schemas/search-report.schema.json +8 -1
- package/dist/universal/.github/skills/citable/schemas/summary.schema.json +41 -0
- package/dist/universal/.kiro/skills/citable/SKILL.md +11 -2
- package/dist/universal/.kiro/skills/citable/VERSION +1 -1
- package/dist/universal/.kiro/skills/citable/manifest.json +24 -12
- package/dist/universal/.kiro/skills/citable/schemas/action-plan.schema.json +38 -1
- package/dist/universal/.kiro/skills/citable/schemas/audit-coverage.schema.json +118 -0
- package/dist/universal/.kiro/skills/citable/schemas/claim-evidence.schema.json +21 -0
- package/dist/universal/.kiro/skills/citable/schemas/claim-verification.schema.json +18 -0
- package/dist/universal/.kiro/skills/citable/schemas/collection-result.schema.json +35 -0
- package/dist/universal/.kiro/skills/citable/schemas/config.schema.json +2 -0
- package/dist/universal/.kiro/skills/citable/schemas/cro-report.schema.json +8 -1
- package/dist/universal/.kiro/skills/citable/schemas/derivation.schema.json +18 -0
- package/dist/universal/.kiro/skills/citable/schemas/external-comparison.schema.json +16 -0
- package/dist/universal/.kiro/skills/citable/schemas/external-observation.schema.json +32 -0
- package/dist/universal/.kiro/skills/citable/schemas/finding.schema.json +105 -2
- package/dist/universal/.kiro/skills/citable/schemas/prompt-set.schema.json +47 -0
- package/dist/universal/.kiro/skills/citable/schemas/remediation-verification.schema.json +12 -1
- package/dist/universal/.kiro/skills/citable/schemas/representation-data.schema.json +21 -0
- package/dist/universal/.kiro/skills/citable/schemas/research-dataset.schema.json +27 -0
- package/dist/universal/.kiro/skills/citable/schemas/run.schema.json +15 -2
- package/dist/universal/.kiro/skills/citable/schemas/search-discovery-data.schema.json +20 -0
- package/dist/universal/.kiro/skills/citable/schemas/search-report.schema.json +8 -1
- package/dist/universal/.kiro/skills/citable/schemas/summary.schema.json +41 -0
- package/dist/universal/.opencode/skills/citable/SKILL.md +11 -2
- package/dist/universal/.opencode/skills/citable/VERSION +1 -1
- package/dist/universal/.opencode/skills/citable/manifest.json +24 -12
- package/dist/universal/.opencode/skills/citable/schemas/action-plan.schema.json +38 -1
- package/dist/universal/.opencode/skills/citable/schemas/audit-coverage.schema.json +118 -0
- package/dist/universal/.opencode/skills/citable/schemas/claim-evidence.schema.json +21 -0
- package/dist/universal/.opencode/skills/citable/schemas/claim-verification.schema.json +18 -0
- package/dist/universal/.opencode/skills/citable/schemas/collection-result.schema.json +35 -0
- package/dist/universal/.opencode/skills/citable/schemas/config.schema.json +2 -0
- package/dist/universal/.opencode/skills/citable/schemas/cro-report.schema.json +8 -1
- package/dist/universal/.opencode/skills/citable/schemas/derivation.schema.json +18 -0
- package/dist/universal/.opencode/skills/citable/schemas/external-comparison.schema.json +16 -0
- package/dist/universal/.opencode/skills/citable/schemas/external-observation.schema.json +32 -0
- package/dist/universal/.opencode/skills/citable/schemas/finding.schema.json +105 -2
- package/dist/universal/.opencode/skills/citable/schemas/prompt-set.schema.json +47 -0
- package/dist/universal/.opencode/skills/citable/schemas/remediation-verification.schema.json +12 -1
- package/dist/universal/.opencode/skills/citable/schemas/representation-data.schema.json +21 -0
- package/dist/universal/.opencode/skills/citable/schemas/research-dataset.schema.json +27 -0
- package/dist/universal/.opencode/skills/citable/schemas/run.schema.json +15 -2
- package/dist/universal/.opencode/skills/citable/schemas/search-discovery-data.schema.json +20 -0
- package/dist/universal/.opencode/skills/citable/schemas/search-report.schema.json +8 -1
- package/dist/universal/.opencode/skills/citable/schemas/summary.schema.json +41 -0
- package/dist/universal/.pi/agent/skills/citable/SKILL.md +11 -2
- package/dist/universal/.pi/agent/skills/citable/VERSION +1 -1
- package/dist/universal/.pi/agent/skills/citable/manifest.json +24 -12
- package/dist/universal/.pi/agent/skills/citable/schemas/action-plan.schema.json +38 -1
- package/dist/universal/.pi/agent/skills/citable/schemas/audit-coverage.schema.json +118 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/claim-evidence.schema.json +21 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/claim-verification.schema.json +18 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/collection-result.schema.json +35 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/config.schema.json +2 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/cro-report.schema.json +8 -1
- package/dist/universal/.pi/agent/skills/citable/schemas/derivation.schema.json +18 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/external-comparison.schema.json +16 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/external-observation.schema.json +32 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/finding.schema.json +105 -2
- package/dist/universal/.pi/agent/skills/citable/schemas/prompt-set.schema.json +47 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/remediation-verification.schema.json +12 -1
- package/dist/universal/.pi/agent/skills/citable/schemas/representation-data.schema.json +21 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/research-dataset.schema.json +27 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/run.schema.json +15 -2
- package/dist/universal/.pi/agent/skills/citable/schemas/search-discovery-data.schema.json +20 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/search-report.schema.json +8 -1
- package/dist/universal/.pi/agent/skills/citable/schemas/summary.schema.json +41 -0
- package/dist/universal/.qoder/skills/citable/SKILL.md +11 -2
- package/dist/universal/.qoder/skills/citable/VERSION +1 -1
- package/dist/universal/.qoder/skills/citable/manifest.json +24 -12
- package/dist/universal/.qoder/skills/citable/schemas/action-plan.schema.json +38 -1
- package/dist/universal/.qoder/skills/citable/schemas/audit-coverage.schema.json +118 -0
- package/dist/universal/.qoder/skills/citable/schemas/claim-evidence.schema.json +21 -0
- package/dist/universal/.qoder/skills/citable/schemas/claim-verification.schema.json +18 -0
- package/dist/universal/.qoder/skills/citable/schemas/collection-result.schema.json +35 -0
- package/dist/universal/.qoder/skills/citable/schemas/config.schema.json +2 -0
- package/dist/universal/.qoder/skills/citable/schemas/cro-report.schema.json +8 -1
- package/dist/universal/.qoder/skills/citable/schemas/derivation.schema.json +18 -0
- package/dist/universal/.qoder/skills/citable/schemas/external-comparison.schema.json +16 -0
- package/dist/universal/.qoder/skills/citable/schemas/external-observation.schema.json +32 -0
- package/dist/universal/.qoder/skills/citable/schemas/finding.schema.json +105 -2
- package/dist/universal/.qoder/skills/citable/schemas/prompt-set.schema.json +47 -0
- package/dist/universal/.qoder/skills/citable/schemas/remediation-verification.schema.json +12 -1
- package/dist/universal/.qoder/skills/citable/schemas/representation-data.schema.json +21 -0
- package/dist/universal/.qoder/skills/citable/schemas/research-dataset.schema.json +27 -0
- package/dist/universal/.qoder/skills/citable/schemas/run.schema.json +15 -2
- package/dist/universal/.qoder/skills/citable/schemas/search-discovery-data.schema.json +20 -0
- package/dist/universal/.qoder/skills/citable/schemas/search-report.schema.json +8 -1
- package/dist/universal/.qoder/skills/citable/schemas/summary.schema.json +41 -0
- package/dist/universal/.rovodev/skills/citable/SKILL.md +11 -2
- package/dist/universal/.rovodev/skills/citable/VERSION +1 -1
- package/dist/universal/.rovodev/skills/citable/manifest.json +24 -12
- package/dist/universal/.rovodev/skills/citable/schemas/action-plan.schema.json +38 -1
- package/dist/universal/.rovodev/skills/citable/schemas/audit-coverage.schema.json +118 -0
- package/dist/universal/.rovodev/skills/citable/schemas/claim-evidence.schema.json +21 -0
- package/dist/universal/.rovodev/skills/citable/schemas/claim-verification.schema.json +18 -0
- package/dist/universal/.rovodev/skills/citable/schemas/collection-result.schema.json +35 -0
- package/dist/universal/.rovodev/skills/citable/schemas/config.schema.json +2 -0
- package/dist/universal/.rovodev/skills/citable/schemas/cro-report.schema.json +8 -1
- package/dist/universal/.rovodev/skills/citable/schemas/derivation.schema.json +18 -0
- package/dist/universal/.rovodev/skills/citable/schemas/external-comparison.schema.json +16 -0
- package/dist/universal/.rovodev/skills/citable/schemas/external-observation.schema.json +32 -0
- package/dist/universal/.rovodev/skills/citable/schemas/finding.schema.json +105 -2
- package/dist/universal/.rovodev/skills/citable/schemas/prompt-set.schema.json +47 -0
- package/dist/universal/.rovodev/skills/citable/schemas/remediation-verification.schema.json +12 -1
- package/dist/universal/.rovodev/skills/citable/schemas/representation-data.schema.json +21 -0
- package/dist/universal/.rovodev/skills/citable/schemas/research-dataset.schema.json +27 -0
- package/dist/universal/.rovodev/skills/citable/schemas/run.schema.json +15 -2
- package/dist/universal/.rovodev/skills/citable/schemas/search-discovery-data.schema.json +20 -0
- package/dist/universal/.rovodev/skills/citable/schemas/search-report.schema.json +8 -1
- package/dist/universal/.rovodev/skills/citable/schemas/summary.schema.json +41 -0
- package/dist/universal/.trae/skills/citable/SKILL.md +11 -2
- package/dist/universal/.trae/skills/citable/VERSION +1 -1
- package/dist/universal/.trae/skills/citable/manifest.json +24 -12
- package/dist/universal/.trae/skills/citable/schemas/action-plan.schema.json +38 -1
- package/dist/universal/.trae/skills/citable/schemas/audit-coverage.schema.json +118 -0
- package/dist/universal/.trae/skills/citable/schemas/claim-evidence.schema.json +21 -0
- package/dist/universal/.trae/skills/citable/schemas/claim-verification.schema.json +18 -0
- package/dist/universal/.trae/skills/citable/schemas/collection-result.schema.json +35 -0
- package/dist/universal/.trae/skills/citable/schemas/config.schema.json +2 -0
- package/dist/universal/.trae/skills/citable/schemas/cro-report.schema.json +8 -1
- package/dist/universal/.trae/skills/citable/schemas/derivation.schema.json +18 -0
- package/dist/universal/.trae/skills/citable/schemas/external-comparison.schema.json +16 -0
- package/dist/universal/.trae/skills/citable/schemas/external-observation.schema.json +32 -0
- package/dist/universal/.trae/skills/citable/schemas/finding.schema.json +105 -2
- package/dist/universal/.trae/skills/citable/schemas/prompt-set.schema.json +47 -0
- package/dist/universal/.trae/skills/citable/schemas/remediation-verification.schema.json +12 -1
- package/dist/universal/.trae/skills/citable/schemas/representation-data.schema.json +21 -0
- package/dist/universal/.trae/skills/citable/schemas/research-dataset.schema.json +27 -0
- package/dist/universal/.trae/skills/citable/schemas/run.schema.json +15 -2
- package/dist/universal/.trae/skills/citable/schemas/search-discovery-data.schema.json +20 -0
- package/dist/universal/.trae/skills/citable/schemas/search-report.schema.json +8 -1
- package/dist/universal/.trae/skills/citable/schemas/summary.schema.json +41 -0
- package/dist/universal/.trae-cn/skills/citable/SKILL.md +11 -2
- package/dist/universal/.trae-cn/skills/citable/VERSION +1 -1
- package/dist/universal/.trae-cn/skills/citable/manifest.json +24 -12
- package/dist/universal/.trae-cn/skills/citable/schemas/action-plan.schema.json +38 -1
- package/dist/universal/.trae-cn/skills/citable/schemas/audit-coverage.schema.json +118 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/claim-evidence.schema.json +21 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/claim-verification.schema.json +18 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/collection-result.schema.json +35 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/config.schema.json +2 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/cro-report.schema.json +8 -1
- package/dist/universal/.trae-cn/skills/citable/schemas/derivation.schema.json +18 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/external-comparison.schema.json +16 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/external-observation.schema.json +32 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/finding.schema.json +105 -2
- package/dist/universal/.trae-cn/skills/citable/schemas/prompt-set.schema.json +47 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/remediation-verification.schema.json +12 -1
- package/dist/universal/.trae-cn/skills/citable/schemas/representation-data.schema.json +21 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/research-dataset.schema.json +27 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/run.schema.json +15 -2
- package/dist/universal/.trae-cn/skills/citable/schemas/search-discovery-data.schema.json +20 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/search-report.schema.json +8 -1
- package/dist/universal/.trae-cn/skills/citable/schemas/summary.schema.json +41 -0
- package/dist/universal/manifest.json +292 -148
- package/package.json +1 -1
- package/schemas/action-plan.schema.json +38 -1
- package/schemas/audit-coverage.schema.json +118 -0
- package/schemas/claim-evidence.schema.json +21 -0
- package/schemas/claim-verification.schema.json +18 -0
- package/schemas/collection-result.schema.json +35 -0
- package/schemas/config.schema.json +2 -0
- package/schemas/cro-report.schema.json +8 -1
- package/schemas/derivation.schema.json +18 -0
- package/schemas/external-comparison.schema.json +16 -0
- package/schemas/external-observation.schema.json +32 -0
- package/schemas/finding.schema.json +105 -2
- package/schemas/prompt-set.schema.json +47 -0
- package/schemas/remediation-verification.schema.json +12 -1
- package/schemas/representation-data.schema.json +21 -0
- package/schemas/research-dataset.schema.json +27 -0
- package/schemas/run.schema.json +15 -2
- package/schemas/search-discovery-data.schema.json +20 -0
- package/schemas/search-report.schema.json +8 -1
- package/schemas/summary.schema.json +41 -0
- package/skill/SKILL.md +11 -2
- package/src/acceptance/reproducibility.js +5 -25
- package/src/analysis/croRoadmap.js +3 -3
- package/src/analysis/strategicRoadmap.js +3 -3
- package/src/artifacts/interchange.js +2 -6
- package/src/cli/index.js +15 -3
- package/src/commands/actionPlan.js +46 -5
- package/src/commands/applyRemediation.js +5 -5
- package/src/commands/audit.js +75 -8
- package/src/commands/ciWorkflow.js +7 -2
- package/src/commands/compareSnapshots.js +155 -47
- package/src/commands/connect.js +24 -4
- package/src/commands/context.js +41 -2
- package/src/commands/croSuite.js +51 -13
- package/src/commands/delivery.js +7 -7
- package/src/commands/governance.js +9 -10
- package/src/commands/implementationKit.js +2 -1
- package/src/commands/init.js +8 -1
- package/src/commands/previewCro.js +1 -1
- package/src/commands/prioritize.js +3 -2
- package/src/commands/reviews.js +31 -5
- package/src/commands/roadmapCmd.js +7 -13
- package/src/commands/sweep.js +15 -2
- package/src/commands/verifyRemediation.js +94 -19
- package/src/connectors/collectionResult.js +80 -0
- package/src/connectors/ga4.js +46 -11
- package/src/connectors/gsc.js +45 -6
- package/src/connectors/index.js +1 -1
- package/src/connectors/webflow.js +140 -10
- package/src/connectors/wordpress.js +92 -8
- package/src/crawler/fetch.js +220 -24
- package/src/crawler/resourceValidity.js +119 -0
- package/src/crawler/sitemap.js +435 -16
- package/src/crawler/sitemapCollector.js +364 -0
- package/src/crawler/urlIdentity.js +97 -0
- package/src/detectors/agent.js +11 -0
- package/src/detectors/ans.js +16 -1
- package/src/detectors/arch.js +6 -0
- package/src/detectors/claim.js +9 -0
- package/src/detectors/crawl.js +7 -0
- package/src/detectors/cro.js +21 -0
- package/src/detectors/cwv.js +4 -0
- package/src/detectors/entity.js +7 -0
- package/src/detectors/evd.js +9 -0
- package/src/detectors/framework.js +28 -2
- package/src/detectors/geoReco.js +16 -0
- package/src/detectors/hreflang.js +3 -0
- package/src/detectors/lifeMeas.js +9 -0
- package/src/detectors/link.js +6 -1
- package/src/detectors/page.js +11 -0
- package/src/detectors/schemaData.js +16 -1
- package/src/detectors/tech.js +34 -3
- package/src/discovery/searchConsole.js +186 -0
- package/src/evidence/coverage.js +448 -0
- package/src/evidence/determination.js +157 -0
- package/src/evidence/downstream.js +110 -0
- package/src/evidence/external.js +251 -0
- package/src/evidence/hashes.js +109 -0
- package/src/evidence/run.js +68 -8
- package/src/extractor/page.js +29 -2
- package/src/extractor/site.js +298 -50
- package/src/reporting/executiveCroReport.js +36 -13
- package/src/reporting/executiveExport.js +45 -16
- package/src/reporting/executiveSearchReport.js +56 -22
- package/src/reporting/report.js +56 -9
- package/src/representation/prompts.js +117 -0
- package/src/representation/representation.js +186 -0
- package/src/shared/epistemicLanguage.js +86 -0
- package/src/shared/evidenceSourceResolver.js +48 -17
- package/src/shared/htmlEscape.js +8 -4
- package/src/shared/runPackageVerifier.js +99 -8
- package/src/shared/verifiedRunLoader.js +129 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared semantics for consumers that summarize or act on an evidence package.
|
|
3
|
+
* A consumer may describe the evaluated subset, but may not widen it to the
|
|
4
|
+
* requested scope when collection was incomplete or empty.
|
|
5
|
+
*/
|
|
6
|
+
const DETERMINATION_STATUSES = new Set(['supported', 'qualified', 'indeterminate', 'not_applicable']);
|
|
7
|
+
import { validateAgainst } from '../shared/schemaValidator.js';
|
|
8
|
+
|
|
9
|
+
function populations(coverage) {
|
|
10
|
+
return coverage?.populations && typeof coverage.populations === 'object'
|
|
11
|
+
? coverage.populations : {};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function contractStatus(coverage) {
|
|
15
|
+
if (!coverage || typeof coverage !== 'object') return { valid: false, errors: ['coverage artifact unavailable'] };
|
|
16
|
+
const result = validateAgainst('audit-coverage.schema.json', coverage);
|
|
17
|
+
return { valid: result.valid, errors: result.errors || [] };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function coverageDenominator(coverage) {
|
|
21
|
+
const value = populations(coverage).evaluated;
|
|
22
|
+
return Number.isInteger(value) && value > 0 ? value : 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function completeLocalCoverage(pages, baseUrl = 'https://example.test') {
|
|
26
|
+
const origin = new URL(baseUrl).origin;
|
|
27
|
+
const resources = pages.map((page, index) => ({
|
|
28
|
+
resource_id: `LOCAL-${index + 1}`,
|
|
29
|
+
normalized_url: page.url || `${origin}/page-${index + 1}`,
|
|
30
|
+
original_urls: [page.url || `${origin}/page-${index + 1}`],
|
|
31
|
+
state: 'evaluated',
|
|
32
|
+
evaluation: {},
|
|
33
|
+
}));
|
|
34
|
+
const n = resources.length;
|
|
35
|
+
return {
|
|
36
|
+
schema_version: 1,
|
|
37
|
+
normalization_version: 'url-identity-v1',
|
|
38
|
+
requested_scope: { start_url: `${origin}/`, origin, max_pages: 500, time_budget_seconds: 1800 },
|
|
39
|
+
discovery: { status: 'complete', methods: ['built_output'], limitations: [] },
|
|
40
|
+
populations: { discovered: n, eligible: n, excluded: 0, attempted: n, retrieved: n, valid_resource: n, evaluated: n, valid_but_unevaluated: 0, failed: 0, indeterminate: 0, unvisited: 0 },
|
|
41
|
+
resources,
|
|
42
|
+
stop_reason: 'frontier_exhausted',
|
|
43
|
+
coverage_status: 'complete',
|
|
44
|
+
reconciliation: { valid: true, errors: [] },
|
|
45
|
+
limitations: [],
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function downstreamStatus(coverage) {
|
|
50
|
+
if (!coverage) return 'indeterminate';
|
|
51
|
+
const contract = contractStatus(coverage);
|
|
52
|
+
const denominator = coverageDenominator(coverage);
|
|
53
|
+
const p = populations(coverage);
|
|
54
|
+
const completePopulation = p.failed === 0 && p.indeterminate === 0 && p.unvisited === 0 && p.valid_but_unevaluated === 0;
|
|
55
|
+
if (coverage.coverage_status === 'complete' && denominator > 0 && completePopulation && contract.valid && coverage.reconciliation?.valid === true) return 'supported';
|
|
56
|
+
if (coverage.coverage_status === 'truncated' && denominator > 0
|
|
57
|
+
&& contract.valid && coverage.reconciliation?.valid === true) return 'qualified';
|
|
58
|
+
return 'indeterminate';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function coverageLimitations(coverage) {
|
|
62
|
+
if (!coverage) return ['coverage artifact unavailable; scope is not established'];
|
|
63
|
+
const p = populations(coverage);
|
|
64
|
+
const limitations = Array.isArray(coverage.limitations) ? [...coverage.limitations] : [];
|
|
65
|
+
const contract = contractStatus(coverage);
|
|
66
|
+
if (!contract.valid) limitations.push('coverage artifact does not satisfy the coverage contract');
|
|
67
|
+
if (coverage.coverage_status !== 'complete') {
|
|
68
|
+
limitations.push(`coverage is ${coverage.coverage_status || 'unknown'}; conclusions are limited to evaluated resources`);
|
|
69
|
+
}
|
|
70
|
+
for (const key of ['failed', 'indeterminate', 'unvisited', 'valid_but_unevaluated']) {
|
|
71
|
+
if (Number.isInteger(p[key]) && p[key] > 0) limitations.push(`${p[key]} ${key} resource(s) omitted from determinations`);
|
|
72
|
+
}
|
|
73
|
+
return [...new Set(limitations)];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function downstreamEnvelope(coverage, extra = {}) {
|
|
77
|
+
const p = populations(coverage);
|
|
78
|
+
const denominator = coverageDenominator(coverage);
|
|
79
|
+
const status = downstreamStatus(coverage);
|
|
80
|
+
if (!DETERMINATION_STATUSES.has(status)) throw new Error(`invalid downstream determination status: ${status}`);
|
|
81
|
+
return {
|
|
82
|
+
coverage_status: coverage?.coverage_status || 'indeterminate',
|
|
83
|
+
determination_status: status,
|
|
84
|
+
evidence_scope: {
|
|
85
|
+
requirement: 'evaluated_subset',
|
|
86
|
+
satisfaction: status,
|
|
87
|
+
coverage_ref: coverage ? 'coverage.json' : null,
|
|
88
|
+
resource_ids: Array.isArray(coverage?.resources)
|
|
89
|
+
? coverage.resources.map((resource) => resource.resource_id).filter(Boolean)
|
|
90
|
+
: [],
|
|
91
|
+
population: denominator > 0 ? 'evaluated_subset' : 'none_observed',
|
|
92
|
+
evaluated: denominator,
|
|
93
|
+
eligible: Number.isInteger(p.eligible) ? p.eligible : null,
|
|
94
|
+
excluded: Number.isInteger(p.excluded) ? p.excluded : null,
|
|
95
|
+
failed: Number.isInteger(p.failed) ? p.failed : null,
|
|
96
|
+
indeterminate: Number.isInteger(p.indeterminate) ? p.indeterminate : null,
|
|
97
|
+
unvisited: Number.isInteger(p.unvisited) ? p.unvisited : null,
|
|
98
|
+
denominator,
|
|
99
|
+
},
|
|
100
|
+
limitations: coverageLimitations(coverage),
|
|
101
|
+
...extra,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Return null for empty evidence; never divide by a convenient zero. */
|
|
106
|
+
export function scoreFromCoverage(numerator, coverage, { scale = 100 } = {}) {
|
|
107
|
+
const denominator = coverageDenominator(coverage);
|
|
108
|
+
if (!Number.isFinite(numerator) || denominator === 0) return null;
|
|
109
|
+
return Math.round((numerator / denominator) * scale);
|
|
110
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { canonicalEvidenceJson, createEvidenceHashes } from './hashes.js';
|
|
2
|
+
import { sha256 } from '../shared/io.js';
|
|
3
|
+
import { validateAgainst } from '../shared/schemaValidator.js';
|
|
4
|
+
|
|
5
|
+
const OBSERVATION_SCHEMA = 'external-observation.schema.json';
|
|
6
|
+
const DATASET_SCHEMA = 'research-dataset.schema.json';
|
|
7
|
+
const DERIVATION_SCHEMA = 'derivation.schema.json';
|
|
8
|
+
const CLAIM_SCHEMA = 'claim-evidence.schema.json';
|
|
9
|
+
const COMPARISON_SCHEMA = 'external-comparison.schema.json';
|
|
10
|
+
const VERIFICATION_SCHEMA = 'claim-verification.schema.json';
|
|
11
|
+
|
|
12
|
+
function without(value, key) {
|
|
13
|
+
const copy = { ...value };
|
|
14
|
+
delete copy[key];
|
|
15
|
+
return copy;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function integrity(value, key) {
|
|
19
|
+
return sha256(canonicalEvidenceJson(without(value, key)));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function validate(schema, value, label) {
|
|
23
|
+
const result = validateAgainst(schema, value);
|
|
24
|
+
if (!result.valid) throw new Error(`${label} violates contract: ${result.errors.join('; ')}`);
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function createExternalObservation(input) {
|
|
29
|
+
const item = { ...input };
|
|
30
|
+
item.evidence_hash = integrity(item, 'evidence_hash');
|
|
31
|
+
return validate(OBSERVATION_SCHEMA, item, 'external observation');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function verifyExternalObservation(item) {
|
|
35
|
+
const failures = [];
|
|
36
|
+
const check = validateAgainst(OBSERVATION_SCHEMA, item);
|
|
37
|
+
if (!check.valid) failures.push(...check.errors);
|
|
38
|
+
if (item?.evidence_hash !== integrity(item || {}, 'evidence_hash')) failures.push('evidence hash is inconsistent');
|
|
39
|
+
return { valid: failures.length === 0, failures };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function createResearchDataset(input, { previous = null } = {}) {
|
|
43
|
+
const item = { ...input };
|
|
44
|
+
item.dataset_hash = integrity(item, 'dataset_hash');
|
|
45
|
+
validate(DATASET_SCHEMA, item, 'research dataset');
|
|
46
|
+
if (previous && previous.dataset_id === item.dataset_id && previous.dataset_version === item.dataset_version
|
|
47
|
+
&& previous.dataset_hash !== item.dataset_hash) {
|
|
48
|
+
throw new Error('research dataset version is immutable; create a new dataset_version');
|
|
49
|
+
}
|
|
50
|
+
return item;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function verifyResearchDataset(item, { previous = null } = {}) {
|
|
54
|
+
const failures = [];
|
|
55
|
+
const check = validateAgainst(DATASET_SCHEMA, item);
|
|
56
|
+
if (!check.valid) failures.push(...check.errors);
|
|
57
|
+
if (item?.dataset_hash !== integrity(item || {}, 'dataset_hash')) failures.push('dataset hash is inconsistent');
|
|
58
|
+
if (previous && previous.dataset_id === item?.dataset_id && previous.dataset_version === item?.dataset_version
|
|
59
|
+
&& previous.dataset_hash !== item?.dataset_hash) failures.push('research dataset version is immutable');
|
|
60
|
+
if (failures.length) throw new Error(`research dataset verification failed: ${failures.join('; ')}`);
|
|
61
|
+
return { valid: true, failures: [] };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function createDerivation(input) {
|
|
65
|
+
const item = { ...input };
|
|
66
|
+
item.derivation_hash = integrity(item, 'derivation_hash');
|
|
67
|
+
return validate(DERIVATION_SCHEMA, item, 'derivation');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function verifyDerivation(item) {
|
|
71
|
+
const check = validateAgainst(DERIVATION_SCHEMA, item);
|
|
72
|
+
if (!check.valid || item.derivation_hash !== integrity(item, 'derivation_hash')) return false;
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function createEvidenceClaim(input) {
|
|
77
|
+
const item = { ...input };
|
|
78
|
+
item.claim_hash = integrity(item, 'claim_hash');
|
|
79
|
+
return validate(CLAIM_SCHEMA, item, 'claim evidence');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function verifyClaimIdentity(item) {
|
|
83
|
+
const check = validateAgainst(CLAIM_SCHEMA, item);
|
|
84
|
+
return check.valid && item.claim_hash === integrity(item, 'claim_hash');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function valueAt(object, path) {
|
|
88
|
+
return String(path).split('.').reduce((value, key) => value == null ? undefined : value[key], object);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function reason(code, message) {
|
|
92
|
+
return { code, message };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function evaluatePredicate(assertion, observations) {
|
|
96
|
+
if (!observations.length) return { status: 'NOT_OBSERVED', reasons: [reason('observation_missing', 'No referenced observation was available.')] };
|
|
97
|
+
const results = observations.map((item) => valueAt(item, assertion.path) === assertion.value);
|
|
98
|
+
if (results.every(Boolean)) return { status: 'SUPPORTED', reasons: [] };
|
|
99
|
+
if (results.some(Boolean)) return { status: 'PARTIALLY_SUPPORTED', reasons: [reason('predicate_partial', 'The predicate matched only part of the referenced population.')] };
|
|
100
|
+
return { status: 'CONTRADICTED', reasons: [reason('predicate_contradicted', 'In-scope evidence establishes an incompatible predicate value.')] };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function evaluateAssertion(assertion, observations, derivations) {
|
|
104
|
+
if (!assertion || typeof assertion !== 'object') return { status: 'INDETERMINATE', reasons: [reason('assertion_missing', 'The claim has no structured assertion.')] };
|
|
105
|
+
if (assertion.type === 'predicate' || assertion.type === 'all') return evaluatePredicate(assertion, observations);
|
|
106
|
+
if (assertion.type === 'count') {
|
|
107
|
+
const derivation = derivations[0];
|
|
108
|
+
if (!derivation || !verifyDerivation(derivation)) return { status: 'INDETERMINATE', reasons: [reason('derivation_unavailable', 'The referenced derivation is missing or unverifiable.')] };
|
|
109
|
+
if (derivation.result?.count === assertion.expected) return { status: 'SUPPORTED', reasons: [] };
|
|
110
|
+
return { status: 'CONTRADICTED', reasons: [reason('count_contradicted', 'The preserved derivation result differs from the claimed value.')] };
|
|
111
|
+
}
|
|
112
|
+
if (assertion.type === 'compound') {
|
|
113
|
+
const results = (assertion.assertions || []).map((part) => evaluateAssertion(part, observations, derivations));
|
|
114
|
+
if (!results.length) return { status: 'INDETERMINATE', reasons: [reason('compound_empty', 'The compound claim has no components.')] };
|
|
115
|
+
const reasons = results.flatMap((item) => item.reasons);
|
|
116
|
+
if (results.every((item) => item.status === 'SUPPORTED')) return { status: 'SUPPORTED', reasons };
|
|
117
|
+
if (results.some((item) => item.status === 'CONTRADICTED')) return { status: 'PARTIALLY_SUPPORTED', reasons: [...reasons, reason('compound_component_unsupported', 'At least one compound component is contradicted.')] };
|
|
118
|
+
if (results.some((item) => item.status === 'SUPPORTED')) return { status: 'PARTIALLY_SUPPORTED', reasons: [...reasons, reason('compound_component_unsupported', 'At least one compound component is unsupported.')] };
|
|
119
|
+
return { status: results.some((item) => item.status === 'NOT_OBSERVED') ? 'NOT_OBSERVED' : 'UNSUPPORTED', reasons };
|
|
120
|
+
}
|
|
121
|
+
return { status: 'INDETERMINATE', reasons: [reason('assertion_unsupported', `Assertion type ${assertion.type || 'unknown'} is not supported.`)] };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function scopeSupport(claim, datasets, observations) {
|
|
125
|
+
const scope = claim.declared_scope;
|
|
126
|
+
if (scope.generalization === 'universal') return { status: 'UNSUPPORTED', reasons: [reason('generalization_outside_population', 'The evidence population does not support a universal claim.')] };
|
|
127
|
+
if (scope.type === 'observation' && scope.observation_reference
|
|
128
|
+
&& !(claim.observation_references || []).includes(scope.observation_reference)) {
|
|
129
|
+
return { status: 'INDETERMINATE', reasons: [reason('scope_reference_mismatch', 'The declared observation scope is not among the claim evidence references.')] };
|
|
130
|
+
}
|
|
131
|
+
if (scope.type === 'dataset' && scope.dataset_reference
|
|
132
|
+
&& !(claim.dataset_references || []).includes(scope.dataset_reference)) {
|
|
133
|
+
return { status: 'INDETERMINATE', reasons: [reason('scope_reference_mismatch', 'The declared dataset scope is not among the claim evidence references.')] };
|
|
134
|
+
}
|
|
135
|
+
if (scope.type === 'dataset') {
|
|
136
|
+
const dataset = datasets[0];
|
|
137
|
+
if (!dataset || !verifyDatasetSafe(dataset)) return { status: 'INDETERMINATE', reasons: [reason('dataset_unavailable', 'The declared dataset is missing or unverifiable.')] };
|
|
138
|
+
if (dataset.coverage_status !== 'complete_for_declared_population') return { status: 'PARTIALLY_SUPPORTED', reasons: [reason('dataset_coverage_incomplete', `Dataset coverage is ${dataset.coverage_status}, not complete for the declared population.`)] };
|
|
139
|
+
const referenced = new Set(claim.observation_references || []);
|
|
140
|
+
const missingMembers = (dataset.observation_references || []).filter((id) => !referenced.has(id));
|
|
141
|
+
if (missingMembers.length) return { status: 'PARTIALLY_SUPPORTED', reasons: [reason('dataset_members_missing', `The claim does not reference every observation in the declared dataset: ${missingMembers.join(', ')}`)] };
|
|
142
|
+
}
|
|
143
|
+
if (!observations.length) return { status: 'NOT_OBSERVED', reasons: [reason('observation_missing', 'The declared scope has no observed members.')] };
|
|
144
|
+
return { status: 'SUPPORTED', reasons: [] };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function verifyDatasetSafe(dataset) {
|
|
148
|
+
try { return verifyResearchDataset(dataset).valid; } catch { return false; }
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function datasetReference(dataset) {
|
|
152
|
+
return `${dataset.dataset_id}@${dataset.dataset_version}`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function resolveDatasetReference(reference, datasets) {
|
|
156
|
+
const exact = datasets.filter((item) => datasetReference(item) === reference);
|
|
157
|
+
if (exact.length === 1) return exact[0];
|
|
158
|
+
// Legacy id-only references remain valid only while the id has one version.
|
|
159
|
+
// Once versions diverge, silently selecting one would rewrite historical
|
|
160
|
+
// claim meaning.
|
|
161
|
+
if (!String(reference).includes('@')) {
|
|
162
|
+
const byId = datasets.filter((item) => item.dataset_id === reference);
|
|
163
|
+
return byId.length === 1 ? byId[0] : null;
|
|
164
|
+
}
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function combine(proposition, scope) {
|
|
169
|
+
if (proposition.status === 'NOT_OBSERVED') return 'NOT_OBSERVED';
|
|
170
|
+
if (proposition.status === 'INDETERMINATE' || scope.status === 'INDETERMINATE') return 'INDETERMINATE';
|
|
171
|
+
if (proposition.status === 'CONTRADICTED') return 'CONTRADICTED';
|
|
172
|
+
if (proposition.status === 'PARTIALLY_SUPPORTED' || scope.status === 'PARTIALLY_SUPPORTED' || scope.status === 'UNSUPPORTED') return 'PARTIALLY_SUPPORTED';
|
|
173
|
+
if (proposition.status === 'UNSUPPORTED') return 'UNSUPPORTED';
|
|
174
|
+
return 'SUPPORTED';
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function verifyClaim(claim, { observations = [], datasets = [], derivations = [] } = {}) {
|
|
178
|
+
if (!verifyClaimIdentity(claim)) {
|
|
179
|
+
return {
|
|
180
|
+
claim_id: claim?.claim_id || 'unknown', claim_version: claim?.claim_version || 1,
|
|
181
|
+
overall: 'INDETERMINATE', proposition_support: 'INDETERMINATE', scope_support: 'INDETERMINATE',
|
|
182
|
+
reasons: [reason('claim_unverifiable', 'Claim identity, schema, or integrity hash could not be verified.')],
|
|
183
|
+
lineage: { claim_reference: claim?.claim_id || 'unknown', observation_references: claim?.observation_references || [], dataset_references: claim?.dataset_references || [], derivation_references: claim?.derivation_references || [] }, limitations: ['Claim evidence could not be verified.'],
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
const byId = new Map(observations.map((item) => [item.observation_id, item]));
|
|
187
|
+
const referenced = (claim.observation_references || []).map((id) => byId.get(id)).filter(Boolean);
|
|
188
|
+
const missing = (claim.observation_references || []).filter((id) => !byId.has(id));
|
|
189
|
+
const integrityFailure = referenced.some((item) => !verifyExternalObservation(item).valid);
|
|
190
|
+
let proposition;
|
|
191
|
+
if (integrityFailure) proposition = { status: 'INDETERMINATE', reasons: [reason('evidence_unverifiable', 'Referenced observation integrity verification failed.')] };
|
|
192
|
+
else if (missing.length) proposition = { status: 'NOT_OBSERVED', reasons: [reason('observation_missing', `Missing referenced observations: ${missing.join(', ')}`)] };
|
|
193
|
+
else proposition = evaluateAssertion(claim.assertion, referenced, (claim.derivation_references || []).map((id) => derivations.find((item) => item.derivation_id === id)).filter(Boolean));
|
|
194
|
+
const selectedDatasets = (claim.dataset_references || []).map((reference) => resolveDatasetReference(reference, datasets)).filter(Boolean);
|
|
195
|
+
const missingDatasets = (claim.dataset_references || []).filter((reference) => !resolveDatasetReference(reference, datasets));
|
|
196
|
+
const scope = scopeSupport(claim, selectedDatasets, referenced);
|
|
197
|
+
if (missingDatasets.length && claim.declared_scope.type === 'dataset') {
|
|
198
|
+
scope.status = 'INDETERMINATE';
|
|
199
|
+
scope.reasons.push(reason('dataset_reference_ambiguous_or_missing', `Dataset references could not be resolved exactly: ${missingDatasets.join(', ')}`));
|
|
200
|
+
}
|
|
201
|
+
const result = {
|
|
202
|
+
claim_id: claim.claim_id, claim_version: claim.claim_version, overall: combine(proposition, scope),
|
|
203
|
+
proposition_support: proposition.status, scope_support: scope.status,
|
|
204
|
+
reasons: [...proposition.reasons, ...scope.reasons],
|
|
205
|
+
lineage: { claim_reference: claim.claim_id, observation_references: claim.observation_references || [], dataset_references: claim.dataset_references || [], derivation_references: claim.derivation_references || [] },
|
|
206
|
+
limitations: claim.limitations || [],
|
|
207
|
+
};
|
|
208
|
+
return validate(VERIFICATION_SCHEMA, result, 'claim verification');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const COMPARISON_DIMENSIONS = ['source', 'subject', 'collector', 'parser', 'configuration', 'population', 'normalization', 'dataset_version', 'derivation_version'];
|
|
212
|
+
|
|
213
|
+
function dimensionValue(item, dimension) {
|
|
214
|
+
if (dimension === 'source') return item.source?.provider;
|
|
215
|
+
if (dimension === 'subject') return item.subject?.id;
|
|
216
|
+
if (dimension === 'collector') return `${item.collector?.id || ''}@${item.collector?.version || ''}` || null;
|
|
217
|
+
if (dimension === 'parser') return item.parser ? `${item.parser.id}@${item.parser.version}` : null;
|
|
218
|
+
if (dimension === 'configuration') return item.configuration ? `${item.configuration.id}@${item.configuration.version}` : null;
|
|
219
|
+
if (dimension === 'population') return item.population?.declared?.id || item.declared_population?.id;
|
|
220
|
+
if (dimension === 'normalization') return item.normalization ? `${item.normalization.method}@${item.normalization.version}` : null;
|
|
221
|
+
if (dimension === 'dataset_version') return item.dataset_id ? `${item.dataset_id}@${item.dataset_version}` : null;
|
|
222
|
+
if (dimension === 'derivation_version') return item.derivation_id ? `${item.derivation_id}@${item.derivation_version}` : null;
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function compareItems(left, right, leftReference, rightReference, dimensionsToCompare = COMPARISON_DIMENSIONS) {
|
|
227
|
+
const dimensions = {};
|
|
228
|
+
for (const key of dimensionsToCompare) {
|
|
229
|
+
const a = dimensionValue(left, key); const b = dimensionValue(right, key);
|
|
230
|
+
dimensions[key] = a == null || b == null ? 'missing' : a === b ? 'match' : 'mismatch';
|
|
231
|
+
}
|
|
232
|
+
const mismatches = Object.values(dimensions).filter((x) => x === 'mismatch').length;
|
|
233
|
+
const missing = Object.values(dimensions).filter((x) => x === 'missing').length;
|
|
234
|
+
const status = mismatches ? 'NOT_COMPARABLE' : missing ? 'PARTIALLY_COMPARABLE' : 'COMPARABLE';
|
|
235
|
+
const result = { comparison_id: `CMP-${sha256(canonicalEvidenceJson({ leftReference, rightReference, dimensions })).slice(0, 16)}`, left_reference: leftReference, right_reference: rightReference, status, dimensions, limitations: status === 'COMPARABLE' ? [] : ['No delta should be generalized beyond the aligned dimensions.'] };
|
|
236
|
+
return validate(COMPARISON_SCHEMA, result, 'external comparison');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function compareExternalObservations(left, right) {
|
|
240
|
+
if (!verifyExternalObservation(left).valid || !verifyExternalObservation(right).valid) {
|
|
241
|
+
return { comparison_id: `CMP-${sha256(`${left?.observation_id || ''}:${right?.observation_id || ''}`).slice(0, 16)}`, left_reference: left?.observation_id || 'unknown', right_reference: right?.observation_id || 'unknown', status: 'INDETERMINATE', dimensions: {}, limitations: ['Observation integrity could not be verified.'] };
|
|
242
|
+
}
|
|
243
|
+
return compareItems(left, right, left.observation_id, right.observation_id,
|
|
244
|
+
['source', 'subject', 'collector', 'parser', 'configuration', 'population']);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function compareResearchDatasets(left, right) {
|
|
248
|
+
if (!verifyDatasetSafe(left) || !verifyDatasetSafe(right)) return { comparison_id: 'CMP-invalid', left_reference: left?.dataset_id || 'unknown', right_reference: right?.dataset_id || 'unknown', status: 'INDETERMINATE', dimensions: {}, limitations: ['Dataset integrity could not be verified.'] };
|
|
249
|
+
return compareItems(left, right, `${left.dataset_id}@${left.dataset_version}`, `${right.dataset_id}@${right.dataset_version}`,
|
|
250
|
+
['dataset_version', 'normalization', 'population']);
|
|
251
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { sha256 } from '../shared/io.js';
|
|
2
|
+
|
|
3
|
+
export const EVIDENCE_HASH_SCHEMA_VERSION = 'evidence-hashes-v1';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Canonical JSON used for evidence representations. Object key order is not
|
|
7
|
+
* evidence; array order is preserved because it can be meaningful (for
|
|
8
|
+
* example, JSON-LD graph order and an evaluator's finding order).
|
|
9
|
+
*/
|
|
10
|
+
export function canonicalEvidenceJson(value) {
|
|
11
|
+
if (Array.isArray(value)) return `[${value.map(canonicalEvidenceJson).join(',')}]`;
|
|
12
|
+
if (value && typeof value === 'object') {
|
|
13
|
+
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalEvidenceJson(value[key])}`).join(',')}}`;
|
|
14
|
+
}
|
|
15
|
+
return JSON.stringify(value);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function bytes(value) {
|
|
19
|
+
if (Buffer.isBuffer(value)) return value;
|
|
20
|
+
if (value instanceof Uint8Array) return Buffer.from(value);
|
|
21
|
+
return Buffer.from(String(value ?? ''), 'utf8');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function hashBytes(value) {
|
|
25
|
+
return sha256(bytes(value));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function hashCanonical(value) {
|
|
29
|
+
return sha256(canonicalEvidenceJson(value));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Normalize only extraction whitespace; do not remove content or metadata. */
|
|
33
|
+
export function normalizeExtractedText(value) {
|
|
34
|
+
return String(value ?? '').replace(/\s+/g, ' ').trim();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Build named hashes for representations that are actually available.
|
|
39
|
+
* Null means that representation was not observed; it is never synthesized
|
|
40
|
+
* from another layer.
|
|
41
|
+
*/
|
|
42
|
+
export function createEvidenceHashes({
|
|
43
|
+
responseBody,
|
|
44
|
+
extractedText,
|
|
45
|
+
structuredData,
|
|
46
|
+
renderedDom,
|
|
47
|
+
evidence,
|
|
48
|
+
artifact,
|
|
49
|
+
} = {}) {
|
|
50
|
+
return {
|
|
51
|
+
hash_schema_version: EVIDENCE_HASH_SCHEMA_VERSION,
|
|
52
|
+
response_body_hash: responseBody == null ? null : hashBytes(responseBody),
|
|
53
|
+
rendered_dom_hash: renderedDom == null ? null : hashBytes(renderedDom),
|
|
54
|
+
extracted_text_hash: extractedText == null ? null : hashBytes(normalizeExtractedText(extractedText)),
|
|
55
|
+
structured_data_hash: structuredData == null ? null : hashCanonical(structuredData),
|
|
56
|
+
evidence_hash: evidence == null ? null : hashCanonical(evidence),
|
|
57
|
+
artifact_hash: artifact == null ? null : hashCanonical(artifact),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Explicit legacy meaning for snapshots and connector payloads during migration. */
|
|
62
|
+
export const LEGACY_CONTENT_HASH_SEMANTICS = 'extracted_text_v1';
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Canonical shape of the persisted pages/index.json record. The artifact hash
|
|
66
|
+
* is intentionally excluded so the hash does not contain itself; verification
|
|
67
|
+
* recomputes this exact record shape and compares it with the stored claim.
|
|
68
|
+
*/
|
|
69
|
+
export function pageArtifactRecord(page) {
|
|
70
|
+
return {
|
|
71
|
+
url: page.url,
|
|
72
|
+
status: page.status,
|
|
73
|
+
title: page.title,
|
|
74
|
+
canonicals: page.canonicals,
|
|
75
|
+
noindex: page.noindex,
|
|
76
|
+
wordCount: page.wordCount,
|
|
77
|
+
sourceFile: page.sourceFile,
|
|
78
|
+
requested_url: page.requestedUrl ?? null,
|
|
79
|
+
effective_url: page.url,
|
|
80
|
+
declared_canonical_url: page.canonicals?.[0] ?? null,
|
|
81
|
+
resource_id: page.urlIdentity?.resource_id ?? null,
|
|
82
|
+
url_identity: page.urlIdentity ?? null,
|
|
83
|
+
hash_schema_version: page.hash_schema_version ?? null,
|
|
84
|
+
response_body_hash: page.response_body_hash ?? null,
|
|
85
|
+
rendered_dom_hash: page.rendered_dom_hash ?? null,
|
|
86
|
+
extracted_text_hash: page.extracted_text_hash ?? null,
|
|
87
|
+
structured_data_hash: page.structured_data_hash ?? null,
|
|
88
|
+
evidence_hash: page.evidence_hash ?? null,
|
|
89
|
+
contentHash: page.extracted_text_hash ?? null,
|
|
90
|
+
hash_semantics: LEGACY_CONTENT_HASH_SEMANTICS,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function hashPageArtifact(page) {
|
|
95
|
+
return hashCanonical(pageArtifactRecord(page));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Bind a determination/finding to the representation its hash actually covers. */
|
|
99
|
+
export function evidenceIdentity(hashes, representation) {
|
|
100
|
+
const field = `${representation}_hash`;
|
|
101
|
+
const hash = hashes?.[field];
|
|
102
|
+
if (!['response_body', 'rendered_dom', 'extracted_text', 'structured_data', 'evidence', 'artifact'].includes(representation)) {
|
|
103
|
+
throw new TypeError(`unknown evidence representation: ${representation}`);
|
|
104
|
+
}
|
|
105
|
+
if (typeof hash !== 'string' || !/^[a-f0-9]{64}$/.test(hash)) {
|
|
106
|
+
throw new Error(`evidence representation is unavailable: ${representation}`);
|
|
107
|
+
}
|
|
108
|
+
return { hash_schema_version: hashes.hash_schema_version ?? EVIDENCE_HASH_SCHEMA_VERSION, representation, hash };
|
|
109
|
+
}
|
package/src/evidence/run.js
CHANGED
|
@@ -23,6 +23,26 @@ function gitInfo(root) {
|
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
function assertSafeArtifactPath(runDir, relativePath) {
|
|
27
|
+
if (typeof relativePath !== 'string' || !relativePath || path.isAbsolute(relativePath) || relativePath.includes('\\')) {
|
|
28
|
+
throw new Error(`unsafe artifact path: ${relativePath}`);
|
|
29
|
+
}
|
|
30
|
+
const parts = relativePath.split('/');
|
|
31
|
+
if (parts.some((part) => !part || part === '.' || part === '..')) throw new Error(`unsafe artifact path: ${relativePath}`);
|
|
32
|
+
let current = runDir;
|
|
33
|
+
for (const part of parts) {
|
|
34
|
+
current = path.join(current, part);
|
|
35
|
+
if (!fs.existsSync(current)) continue;
|
|
36
|
+
const stat = fs.lstatSync(current);
|
|
37
|
+
if (stat.isSymbolicLink()) throw new Error(`artifact path contains symbolic link: ${relativePath}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function fsyncPath(file) {
|
|
42
|
+
const handle = fs.openSync(file, 'r');
|
|
43
|
+
try { fs.fsyncSync(handle); } finally { fs.closeSync(handle); }
|
|
44
|
+
}
|
|
45
|
+
|
|
26
46
|
/** Create a run evidence package under <root>/.citable/runs/<run-id>/. */
|
|
27
47
|
export function createRun(root, { command, argv = [], target, locale = process.env.LANG || 'en', jurisdiction = null, configHash = null }) {
|
|
28
48
|
const runId = newRunId(command);
|
|
@@ -30,6 +50,7 @@ export function createRun(root, { command, argv = [], target, locale = process.e
|
|
|
30
50
|
fs.mkdirSync(dir, { recursive: true });
|
|
31
51
|
const git = gitInfo(root);
|
|
32
52
|
const manifest = {
|
|
53
|
+
schema_version: 2,
|
|
33
54
|
run_id: runId,
|
|
34
55
|
command,
|
|
35
56
|
argv,
|
|
@@ -50,6 +71,9 @@ export function createRun(root, { command, argv = [], target, locale = process.e
|
|
|
50
71
|
errors: [],
|
|
51
72
|
warnings: [],
|
|
52
73
|
status: 'incomplete',
|
|
74
|
+
execution_status: 'completed',
|
|
75
|
+
coverage_status: 'not_applicable',
|
|
76
|
+
determination_status: 'not_applicable',
|
|
53
77
|
};
|
|
54
78
|
return {
|
|
55
79
|
runId,
|
|
@@ -59,6 +83,7 @@ export function createRun(root, { command, argv = [], target, locale = process.e
|
|
|
59
83
|
manifest.input_hashes[name] = sha256(typeof content === 'string' ? content : JSON.stringify(content));
|
|
60
84
|
},
|
|
61
85
|
writeArtifact(relPath, data) {
|
|
86
|
+
assertSafeArtifactPath(dir, relPath);
|
|
62
87
|
const file = path.join(dir, relPath);
|
|
63
88
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
64
89
|
if (typeof data === 'string' || Buffer.isBuffer(data)) fs.writeFileSync(file, data);
|
|
@@ -70,18 +95,53 @@ export function createRun(root, { command, argv = [], target, locale = process.e
|
|
|
70
95
|
manifest.status = status;
|
|
71
96
|
const { valid, errors } = validateAgainst('run.schema.json', manifest);
|
|
72
97
|
if (!valid) throw new Error(`run manifest invalid: ${errors.join('; ')}`);
|
|
73
|
-
|
|
98
|
+
const stage = `${dir}.sealing-${process.pid}-${Math.random().toString(36).slice(2, 8)}`;
|
|
99
|
+
fs.rmSync(stage, { recursive: true, force: true });
|
|
100
|
+
fs.cpSync(dir, stage, { recursive: true, dereference: false, errorOnExist: true });
|
|
101
|
+
writeJson(path.join(stage, 'manifest.json'), manifest);
|
|
102
|
+
const summaryFile = path.join(stage, 'summary.json');
|
|
103
|
+
if (fs.existsSync(summaryFile)) {
|
|
104
|
+
// Keep the persisted ordering contract deterministic even on filesystems
|
|
105
|
+
// with coarse timestamp resolution or delayed copy metadata updates.
|
|
106
|
+
const now = new Date(Date.now() + 1000);
|
|
107
|
+
fs.utimesSync(summaryFile, now, now);
|
|
108
|
+
}
|
|
74
109
|
// checksums over every artifact in the package
|
|
75
|
-
const
|
|
110
|
+
const checksumEntries = [];
|
|
76
111
|
const walk = (d) => {
|
|
77
|
-
for (const
|
|
78
|
-
const p = path.join(d, name);
|
|
79
|
-
if (
|
|
80
|
-
|
|
112
|
+
for (const entry of fs.readdirSync(d, { withFileTypes: true })) {
|
|
113
|
+
const p = path.join(d, entry.name);
|
|
114
|
+
if (entry.isSymbolicLink()) throw new Error(`staging package contains symbolic link: ${path.relative(stage, p)}`);
|
|
115
|
+
if (entry.isDirectory()) walk(p);
|
|
116
|
+
else if (entry.isFile() && entry.name !== 'checksums.json') checksumEntries.push([path.relative(stage, p).split(path.sep).join('/'), sha256File(p)]);
|
|
117
|
+
else if (!entry.isFile()) throw new Error(`staging package contains unsupported entry: ${path.relative(stage, p)}`);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
walk(stage);
|
|
121
|
+
const checksums = Object.fromEntries(checksumEntries.sort(([a], [b]) => a.localeCompare(b)));
|
|
122
|
+
writeJson(path.join(stage, 'checksums.json'), checksums);
|
|
123
|
+
const fsyncTree = (directory) => {
|
|
124
|
+
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
|
125
|
+
const file = path.join(directory, entry.name);
|
|
126
|
+
if (entry.isSymbolicLink()) throw new Error(`staging package contains symbolic link: ${path.relative(stage, file)}`);
|
|
127
|
+
if (entry.isDirectory()) fsyncTree(file);
|
|
128
|
+
else if (entry.isFile()) fsyncPath(file);
|
|
81
129
|
}
|
|
130
|
+
fsyncPath(directory);
|
|
82
131
|
};
|
|
83
|
-
|
|
84
|
-
|
|
132
|
+
fsyncTree(stage);
|
|
133
|
+
const parent = path.dirname(dir);
|
|
134
|
+
const displaced = `${dir}.partial-${process.pid}-${Math.random().toString(36).slice(2, 8)}`;
|
|
135
|
+
fs.renameSync(dir, displaced);
|
|
136
|
+
try {
|
|
137
|
+
fs.renameSync(stage, dir);
|
|
138
|
+
fs.rmSync(displaced, { recursive: true, force: true });
|
|
139
|
+
fsyncPath(parent);
|
|
140
|
+
} catch (error) {
|
|
141
|
+
if (!fs.existsSync(dir) && fs.existsSync(displaced)) fs.renameSync(displaced, dir);
|
|
142
|
+
fs.rmSync(stage, { recursive: true, force: true });
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
85
145
|
return dir;
|
|
86
146
|
},
|
|
87
147
|
};
|
package/src/extractor/page.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { parse } from 'node-html-parser';
|
|
2
|
+
import { createEvidenceHashes, hashPageArtifact } from '../evidence/hashes.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Extract a PageModel from raw HTML plus transport metadata.
|
|
5
6
|
* Everything downstream (detectors, inspect, schema) consumes this model.
|
|
6
7
|
*/
|
|
7
|
-
export function extractPage({ url, html, status = 200, headers = {}, sourceFile = null, redirectChain = [] }) {
|
|
8
|
+
export function extractPage({ url, html, responseBody = null, status = 200, headers = {}, sourceFile = null, redirectChain = [], renderedDom = null }) {
|
|
8
9
|
const root = parse(html, { comment: true });
|
|
9
10
|
const head = root.querySelector('head');
|
|
10
11
|
|
|
@@ -282,7 +283,7 @@ export function extractPage({ url, html, status = 200, headers = {}, sourceFile
|
|
|
282
283
|
};
|
|
283
284
|
walkDom(root, 1);
|
|
284
285
|
|
|
285
|
-
|
|
286
|
+
const page = {
|
|
286
287
|
url,
|
|
287
288
|
sourceFile,
|
|
288
289
|
status,
|
|
@@ -323,4 +324,30 @@ export function extractPage({ url, html, status = 200, headers = {}, sourceFile
|
|
|
323
324
|
domNodeCount,
|
|
324
325
|
maxDomDepth,
|
|
325
326
|
};
|
|
327
|
+
|
|
328
|
+
// Keep representation identity explicit. These hashes deliberately do not
|
|
329
|
+
// share a generic `contentHash`: raw response bytes, extracted text,
|
|
330
|
+
// structured data, evaluator evidence, and the persisted page artifact are
|
|
331
|
+
// different claims and may change independently.
|
|
332
|
+
const hashes = createEvidenceHashes({
|
|
333
|
+
responseBody: responseBody ?? html,
|
|
334
|
+
renderedDom,
|
|
335
|
+
extractedText: text,
|
|
336
|
+
structuredData: jsonLd.length
|
|
337
|
+
? jsonLd.map(({ parsed, blocks, parseError }) => ({ parsed, blocks, parseError }))
|
|
338
|
+
: null,
|
|
339
|
+
evidence: {
|
|
340
|
+
extracted_text: text,
|
|
341
|
+
headings,
|
|
342
|
+
paragraphs,
|
|
343
|
+
structured_data: jsonLd.map(({ parsed, blocks, parseError }) => ({ parsed, blocks, parseError })),
|
|
344
|
+
},
|
|
345
|
+
// This is replaced after URL/resource metadata is attached; keeping the
|
|
346
|
+
// field null here prevents a pre-persistence model from being presented as
|
|
347
|
+
// the persisted artifact identity.
|
|
348
|
+
artifact: null,
|
|
349
|
+
});
|
|
350
|
+
const withHashes = { ...page, ...hashes };
|
|
351
|
+
withHashes.artifact_hash = hashPageArtifact(withHashes);
|
|
352
|
+
return withHashes;
|
|
326
353
|
}
|