@nebulacomponents/citable 1.18.2 → 1.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +64 -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 +19 -13
- 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/browser-evidence-plan.schema.json +186 -3
- package/dist/universal/.agents/skills/citable/schemas/browser-network-events.schema.json +142 -0
- package/dist/universal/.agents/skills/citable/schemas/browser-runtime-events.schema.json +112 -0
- package/dist/universal/.agents/skills/citable/schemas/browser-state-observations.schema.json +97 -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/finding.schema.json +105 -2
- package/dist/universal/.agents/skills/citable/schemas/remediation-verification.schema.json +12 -1
- package/dist/universal/.agents/skills/citable/schemas/run.schema.json +15 -2
- 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 +19 -13
- 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/browser-evidence-plan.schema.json +186 -3
- package/dist/universal/.claude/skills/citable/schemas/browser-network-events.schema.json +142 -0
- package/dist/universal/.claude/skills/citable/schemas/browser-runtime-events.schema.json +112 -0
- package/dist/universal/.claude/skills/citable/schemas/browser-state-observations.schema.json +97 -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/finding.schema.json +105 -2
- package/dist/universal/.claude/skills/citable/schemas/remediation-verification.schema.json +12 -1
- package/dist/universal/.claude/skills/citable/schemas/run.schema.json +15 -2
- 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 +19 -13
- 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/browser-evidence-plan.schema.json +186 -3
- package/dist/universal/.cursor/skills/citable/schemas/browser-network-events.schema.json +142 -0
- package/dist/universal/.cursor/skills/citable/schemas/browser-runtime-events.schema.json +112 -0
- package/dist/universal/.cursor/skills/citable/schemas/browser-state-observations.schema.json +97 -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/finding.schema.json +105 -2
- package/dist/universal/.cursor/skills/citable/schemas/remediation-verification.schema.json +12 -1
- package/dist/universal/.cursor/skills/citable/schemas/run.schema.json +15 -2
- 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 +19 -13
- 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/browser-evidence-plan.schema.json +186 -3
- package/dist/universal/.gemini/skills/citable/schemas/browser-network-events.schema.json +142 -0
- package/dist/universal/.gemini/skills/citable/schemas/browser-runtime-events.schema.json +112 -0
- package/dist/universal/.gemini/skills/citable/schemas/browser-state-observations.schema.json +97 -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/finding.schema.json +105 -2
- package/dist/universal/.gemini/skills/citable/schemas/remediation-verification.schema.json +12 -1
- package/dist/universal/.gemini/skills/citable/schemas/run.schema.json +15 -2
- 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 +19 -13
- 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/browser-evidence-plan.schema.json +186 -3
- package/dist/universal/.github/skills/citable/schemas/browser-network-events.schema.json +142 -0
- package/dist/universal/.github/skills/citable/schemas/browser-runtime-events.schema.json +112 -0
- package/dist/universal/.github/skills/citable/schemas/browser-state-observations.schema.json +97 -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/finding.schema.json +105 -2
- package/dist/universal/.github/skills/citable/schemas/remediation-verification.schema.json +12 -1
- package/dist/universal/.github/skills/citable/schemas/run.schema.json +15 -2
- 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 +19 -13
- 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/browser-evidence-plan.schema.json +186 -3
- package/dist/universal/.kiro/skills/citable/schemas/browser-network-events.schema.json +142 -0
- package/dist/universal/.kiro/skills/citable/schemas/browser-runtime-events.schema.json +112 -0
- package/dist/universal/.kiro/skills/citable/schemas/browser-state-observations.schema.json +97 -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/finding.schema.json +105 -2
- package/dist/universal/.kiro/skills/citable/schemas/remediation-verification.schema.json +12 -1
- package/dist/universal/.kiro/skills/citable/schemas/run.schema.json +15 -2
- 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 +19 -13
- 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/browser-evidence-plan.schema.json +186 -3
- package/dist/universal/.opencode/skills/citable/schemas/browser-network-events.schema.json +142 -0
- package/dist/universal/.opencode/skills/citable/schemas/browser-runtime-events.schema.json +112 -0
- package/dist/universal/.opencode/skills/citable/schemas/browser-state-observations.schema.json +97 -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/finding.schema.json +105 -2
- package/dist/universal/.opencode/skills/citable/schemas/remediation-verification.schema.json +12 -1
- package/dist/universal/.opencode/skills/citable/schemas/run.schema.json +15 -2
- 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 +19 -13
- 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/browser-evidence-plan.schema.json +186 -3
- package/dist/universal/.pi/agent/skills/citable/schemas/browser-network-events.schema.json +142 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/browser-runtime-events.schema.json +112 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/browser-state-observations.schema.json +97 -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/finding.schema.json +105 -2
- package/dist/universal/.pi/agent/skills/citable/schemas/remediation-verification.schema.json +12 -1
- package/dist/universal/.pi/agent/skills/citable/schemas/run.schema.json +15 -2
- 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 +19 -13
- 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/browser-evidence-plan.schema.json +186 -3
- package/dist/universal/.qoder/skills/citable/schemas/browser-network-events.schema.json +142 -0
- package/dist/universal/.qoder/skills/citable/schemas/browser-runtime-events.schema.json +112 -0
- package/dist/universal/.qoder/skills/citable/schemas/browser-state-observations.schema.json +97 -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/finding.schema.json +105 -2
- package/dist/universal/.qoder/skills/citable/schemas/remediation-verification.schema.json +12 -1
- package/dist/universal/.qoder/skills/citable/schemas/run.schema.json +15 -2
- 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 +19 -13
- 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/browser-evidence-plan.schema.json +186 -3
- package/dist/universal/.rovodev/skills/citable/schemas/browser-network-events.schema.json +142 -0
- package/dist/universal/.rovodev/skills/citable/schemas/browser-runtime-events.schema.json +112 -0
- package/dist/universal/.rovodev/skills/citable/schemas/browser-state-observations.schema.json +97 -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/finding.schema.json +105 -2
- package/dist/universal/.rovodev/skills/citable/schemas/remediation-verification.schema.json +12 -1
- package/dist/universal/.rovodev/skills/citable/schemas/run.schema.json +15 -2
- 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 +19 -13
- 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/browser-evidence-plan.schema.json +186 -3
- package/dist/universal/.trae/skills/citable/schemas/browser-network-events.schema.json +142 -0
- package/dist/universal/.trae/skills/citable/schemas/browser-runtime-events.schema.json +112 -0
- package/dist/universal/.trae/skills/citable/schemas/browser-state-observations.schema.json +97 -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/finding.schema.json +105 -2
- package/dist/universal/.trae/skills/citable/schemas/remediation-verification.schema.json +12 -1
- package/dist/universal/.trae/skills/citable/schemas/run.schema.json +15 -2
- 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 +19 -13
- 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/browser-evidence-plan.schema.json +186 -3
- package/dist/universal/.trae-cn/skills/citable/schemas/browser-network-events.schema.json +142 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/browser-runtime-events.schema.json +112 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/browser-state-observations.schema.json +97 -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/finding.schema.json +105 -2
- package/dist/universal/.trae-cn/skills/citable/schemas/remediation-verification.schema.json +12 -1
- package/dist/universal/.trae-cn/skills/citable/schemas/run.schema.json +15 -2
- 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 +232 -160
- package/package.json +1 -1
- package/schemas/action-plan.schema.json +38 -1
- package/schemas/audit-coverage.schema.json +118 -0
- package/schemas/browser-evidence-plan.schema.json +186 -3
- package/schemas/browser-network-events.schema.json +142 -0
- package/schemas/browser-runtime-events.schema.json +112 -0
- package/schemas/browser-state-observations.schema.json +97 -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/finding.schema.json +105 -2
- package/schemas/remediation-verification.schema.json +12 -1
- package/schemas/run.schema.json +15 -2
- 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 +41 -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/evidence/coverage.js +448 -0
- package/src/evidence/determination.js +157 -0
- package/src/evidence/downstream.js +110 -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/observations/browser/correlation.js +83 -0
- package/src/observations/browser/networkCollector.js +413 -0
- package/src/observations/browser/runtimeEventCollector.js +337 -0
- package/src/observations/browser/sanitizer.js +322 -0
- package/src/observations/browser/stateCollector.js +386 -0
- package/src/observations/browserJourney.js +131 -3
- package/src/reporting/executiveCroReport.js +37 -14
- package/src/reporting/executiveExport.js +45 -16
- package/src/reporting/executiveSearchReport.js +57 -23
- package/src/reporting/report.js +56 -9
- 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
- package/src/sow/generateSow.js +1 -1
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coverage requirements for determinations.
|
|
3
|
+
*
|
|
4
|
+
* A requirement describes the population a conclusion is allowed to speak
|
|
5
|
+
* about. It is deliberately separate from the crawler's execution status:
|
|
6
|
+
* observing one page can support a page-local positive finding even when a
|
|
7
|
+
* corpus-wide absence claim is not supportable.
|
|
8
|
+
*/
|
|
9
|
+
export const REQUIREMENTS = Object.freeze({
|
|
10
|
+
PAGE_RESOURCE: 'page_resource',
|
|
11
|
+
EVALUATED_SUBSET: 'evaluated_subset',
|
|
12
|
+
DECLARED_SAMPLE: 'declared_sample',
|
|
13
|
+
EXHAUSTIVE_SCOPE: 'exhaustive_requested_scope',
|
|
14
|
+
RENDERED_RESOURCE: 'rendered_resource',
|
|
15
|
+
PROVIDER_BOUNDED: 'provider_bounded',
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const VALID_REQUIREMENTS = new Set(Object.values(REQUIREMENTS));
|
|
19
|
+
|
|
20
|
+
export function isCoverageRequirement(value) {
|
|
21
|
+
return VALID_REQUIREMENTS.has(value);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function population(coverage) {
|
|
25
|
+
return coverage?.populations && typeof coverage.populations === 'object'
|
|
26
|
+
? coverage.populations
|
|
27
|
+
: {};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function normalizedUrl(value) {
|
|
31
|
+
if (typeof value !== 'string' || !value) return null;
|
|
32
|
+
try {
|
|
33
|
+
const url = new URL(value);
|
|
34
|
+
url.hash = '';
|
|
35
|
+
return url.href;
|
|
36
|
+
} catch {
|
|
37
|
+
return value.split('#', 1)[0];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function resourcesFor(coverage) {
|
|
42
|
+
return Array.isArray(coverage?.resources) ? coverage.resources : [];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function resourceForSubject(coverage, subject) {
|
|
46
|
+
const candidates = [subject?.url, subject?.identifier].map(normalizedUrl).filter(Boolean);
|
|
47
|
+
return resourcesFor(coverage).find((resource) => {
|
|
48
|
+
const resourceUrl = normalizedUrl(resource?.url || resource?.normalized_url);
|
|
49
|
+
return resourceUrl && candidates.includes(resourceUrl);
|
|
50
|
+
}) || null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function completeCoverage(coverage) {
|
|
54
|
+
const p = population(coverage);
|
|
55
|
+
return coverage?.coverage_status === 'complete'
|
|
56
|
+
&& p.unvisited === 0
|
|
57
|
+
&& p.failed === 0
|
|
58
|
+
&& p.indeterminate === 0
|
|
59
|
+
&& p.valid_but_unevaluated === 0
|
|
60
|
+
&& p.evaluated > 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function result(status, extra = {}) {
|
|
64
|
+
return { status, ...extra };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function evaluatePageResource(coverage, subject, rendered = false) {
|
|
68
|
+
const resource = resourceForSubject(coverage, subject);
|
|
69
|
+
if (!resource) return result('indeterminate', { reason: 'resource_not_observed' });
|
|
70
|
+
const state = resource.state || (resource.evaluation ? 'evaluated' : resource.classification?.state);
|
|
71
|
+
if (state !== 'evaluated') {
|
|
72
|
+
return result('indeterminate', { reason: 'resource_not_evaluated', resource_id: resource.resource_id });
|
|
73
|
+
}
|
|
74
|
+
if (rendered && resource.rendered !== true && resource.rendering_status !== 'complete') {
|
|
75
|
+
return result('indeterminate', { reason: 'rendered_observation_unavailable', resource_id: resource.resource_id });
|
|
76
|
+
}
|
|
77
|
+
return result('supported', {
|
|
78
|
+
resource_id: resource.resource_id,
|
|
79
|
+
population: 'evaluated_resource',
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Evaluate whether a coverage envelope satisfies one determination contract.
|
|
85
|
+
* The returned status is intentionally limited to supported, qualified,
|
|
86
|
+
* indeterminate, and not_applicable, matching the finding/run contracts.
|
|
87
|
+
*/
|
|
88
|
+
export function evaluateRequirement(requirement, coverage, subject = null) {
|
|
89
|
+
if (!VALID_REQUIREMENTS.has(requirement)) throw new TypeError(`unknown coverage requirement: ${requirement}`);
|
|
90
|
+
|
|
91
|
+
if (requirement === REQUIREMENTS.PAGE_RESOURCE) return evaluatePageResource(coverage, subject);
|
|
92
|
+
if (requirement === REQUIREMENTS.RENDERED_RESOURCE) return evaluatePageResource(coverage, subject, true);
|
|
93
|
+
|
|
94
|
+
if (requirement === REQUIREMENTS.DECLARED_SAMPLE) {
|
|
95
|
+
const sample = coverage?.sample;
|
|
96
|
+
const numerator = sample?.numerator;
|
|
97
|
+
const denominator = sample?.denominator;
|
|
98
|
+
if (!Number.isInteger(numerator) || !Number.isInteger(denominator) || denominator < 1 || numerator < 0 || numerator > denominator) {
|
|
99
|
+
return result('indeterminate', { reason: 'sample_denominator_unavailable' });
|
|
100
|
+
}
|
|
101
|
+
return { status: 'qualified', population: 'evaluated_subset', numerator, denominator };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (requirement === REQUIREMENTS.PROVIDER_BOUNDED) {
|
|
105
|
+
const p = population(coverage);
|
|
106
|
+
if (!Number.isInteger(p.evaluated) || p.evaluated < 1) return result('indeterminate', { reason: 'no_evaluated_resources' });
|
|
107
|
+
return result('qualified', { population: 'provider_bounded', denominator: p.evaluated });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (requirement === REQUIREMENTS.EVALUATED_SUBSET) {
|
|
111
|
+
// A subject-bearing determination cannot borrow aggregate subset counts:
|
|
112
|
+
// the named page/URL must itself be an evaluator-completed resource.
|
|
113
|
+
if (subject?.type === 'page' || subject?.type === 'url') {
|
|
114
|
+
return evaluatePageResource(coverage, subject);
|
|
115
|
+
}
|
|
116
|
+
const p = population(coverage);
|
|
117
|
+
if (!Number.isInteger(p.evaluated) || p.evaluated < 1) return result('indeterminate', { reason: 'no_evaluated_resources' });
|
|
118
|
+
return result(coverage?.coverage_status === 'complete' ? 'supported' : 'qualified', {
|
|
119
|
+
population: 'evaluated_subset', denominator: p.evaluated,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Exhaustive scope is the only requirement that may support a corpus-wide
|
|
124
|
+
// absence claim. A completed execution with zero resources is not evidence
|
|
125
|
+
// of absence, so require at least one evaluated resource.
|
|
126
|
+
if (completeCoverage(coverage)) {
|
|
127
|
+
return result('supported', { population: 'requested_scope', denominator: population(coverage).evaluated });
|
|
128
|
+
}
|
|
129
|
+
return result('indeterminate', { reason: 'coverage_incomplete' });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Apply coverage semantics to a determination produced by a detector or
|
|
134
|
+
* downstream consumer. Positive page-local observations are preserved; a
|
|
135
|
+
* negative exhaustive conclusion is downgraded when its corpus is incomplete.
|
|
136
|
+
*/
|
|
137
|
+
export function propagateDetermination(determination, coverage) {
|
|
138
|
+
if (!determination || typeof determination !== 'object') throw new TypeError('determination must be an object');
|
|
139
|
+
const current = determination.status;
|
|
140
|
+
if (!['supported', 'qualified', 'indeterminate', 'not_applicable'].includes(current)) {
|
|
141
|
+
throw new TypeError(`invalid determination status: ${current}`);
|
|
142
|
+
}
|
|
143
|
+
if (current !== 'supported') return { ...determination };
|
|
144
|
+
if (determination.polarity !== 'negative' || !determination.requirement) return { ...determination };
|
|
145
|
+
const evaluated = evaluateRequirement(determination.requirement, coverage, determination.subject);
|
|
146
|
+
if (evaluated.status === 'supported') return { ...determination };
|
|
147
|
+
return { ...determination, status: evaluated.status, reason: evaluated.reason || 'coverage_requirement_unsatisfied' };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function requirementForDetector(detector, hit = null) {
|
|
151
|
+
if (!detector || !isCoverageRequirement(detector.coverage_requirement)) {
|
|
152
|
+
throw new TypeError(`detector ${detector?.id ?? '?'} must declare a valid coverage_requirement`);
|
|
153
|
+
}
|
|
154
|
+
// The detector declaration is authoritative. Subject shape must never
|
|
155
|
+
// silently weaken an exhaustive or rendered-resource contract.
|
|
156
|
+
return detector.coverage_requirement;
|
|
157
|
+
}
|
|
@@ -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,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
|
}
|