@nebulacomponents/citable 1.19.0 → 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 +18 -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 +15 -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/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 +15 -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/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 +15 -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/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 +15 -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/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 +15 -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/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 +15 -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/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 +15 -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/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 +15 -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/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 +15 -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/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 +15 -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/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 +15 -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/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 +15 -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/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 +184 -148
- package/package.json +1 -1
- package/schemas/action-plan.schema.json +38 -1
- package/schemas/audit-coverage.schema.json +118 -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/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/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
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
} from '../shared/htmlEscape.js';
|
|
32
32
|
import { extractHostname } from '../shared/domainUtils.js';
|
|
33
33
|
import { validateAgainst } from '../shared/schemaValidator.js';
|
|
34
|
+
import { assertEpistemicLanguage, hasVerifiedEvidenceScope } from '../shared/epistemicLanguage.js';
|
|
34
35
|
|
|
35
36
|
function formatVal(v, suffix = '') {
|
|
36
37
|
if (v === null || v === undefined) return 'NOT OBSERVED';
|
|
@@ -620,6 +621,13 @@ export async function buildExecutiveSearchReport(root, options = {}) {
|
|
|
620
621
|
source_findings_count: findings.length,
|
|
621
622
|
integrity_hash: resolved.integrity_hash,
|
|
622
623
|
synthetic_evidence: isSample,
|
|
624
|
+
package_verified: resolved.package_verified === true,
|
|
625
|
+
integrity_mode: resolved.integrity_mode || 'unknown',
|
|
626
|
+
legacy: resolved.legacy === true,
|
|
627
|
+
coverage_status: resolved.coverage_status || 'indeterminate',
|
|
628
|
+
determination_status: resolved.determination_status || 'indeterminate',
|
|
629
|
+
evaluated: resolved.coverage?.populations?.evaluated ?? null,
|
|
630
|
+
eligible: resolved.coverage?.populations?.eligible ?? null,
|
|
623
631
|
};
|
|
624
632
|
|
|
625
633
|
const report = {
|
|
@@ -667,7 +675,15 @@ export async function buildExecutiveSearchReport(root, options = {}) {
|
|
|
667
675
|
/**
|
|
668
676
|
* Render Search Executive Report as GitHub-flavored Markdown
|
|
669
677
|
*/
|
|
670
|
-
export function renderSearchReportMarkdown(report) {
|
|
678
|
+
export function renderSearchReportMarkdown(report, evidenceContext = null) {
|
|
679
|
+
const context = evidenceContext || report.generation_provenance || {};
|
|
680
|
+
const verifiedScope = hasVerifiedEvidenceScope(context);
|
|
681
|
+
const scopedValue = (value) => {
|
|
682
|
+
if (!verifiedScope && typeof value === 'string' && /\b(?:verified|clean|resolved|optimal|passed)\b|(?:verified_source|clean_profile)/i.test(value)) return 'NOT ESTABLISHED';
|
|
683
|
+
return value;
|
|
684
|
+
};
|
|
685
|
+
const scopedFormat = (value, suffix = '') => scopedValue(formatVal(value, suffix));
|
|
686
|
+
const evidenceRegisterTitle = `${verifiedScope ? 'Verified Evidence Register' : 'Evidence Register (scope-limited; verified status not established)'} (Traceability Engine)`;
|
|
671
687
|
const p = report.pillars;
|
|
672
688
|
const lines = [
|
|
673
689
|
`# ${sanitizeForMarkdown(report.report_title)}`,
|
|
@@ -675,7 +691,7 @@ export function renderSearchReportMarkdown(report) {
|
|
|
675
691
|
`- **Client / Property**: \`${sanitizeForMarkdown(report.client_name)}\` (\`${sanitizeForMarkdown(report.target_domain)}\`)`,
|
|
676
692
|
`- **Generated At**: \`${report.generated_at}\``,
|
|
677
693
|
`- **Overall Search & AEO Readiness Score**: **${report.overall_readiness_score} / 100**`,
|
|
678
|
-
`- **Evidence Register Traceability**: ${report.evidence_register.length}
|
|
694
|
+
`- **Evidence Register Traceability**: ${report.evidence_register.length} evidence references (authority and scope recorded below)`,
|
|
679
695
|
`- **Generation Mode**: \`${report.generation_provenance.generation_mode}\`${report.generation_provenance.synthetic_evidence ? ' *(Synthetic Demo Evidence)*' : ''}`,
|
|
680
696
|
``,
|
|
681
697
|
`> **Operating Premise & Governance Notice**: This executive report presents observable technical search infrastructure, content extraction posture, and controlled citation behavior. In adherence to Citable governance principles, **no search ranking, AI citation, or conversion outcomes are guaranteed**.`,
|
|
@@ -684,7 +700,7 @@ export function renderSearchReportMarkdown(report) {
|
|
|
684
700
|
`## Executive Decision Summary (Traceable Conclusions)`,
|
|
685
701
|
``,
|
|
686
702
|
`### 1. Confirmed Findings (Deterministic Observations)`,
|
|
687
|
-
...report.decision_summary.confirmed_findings.map((f) => `- [x] **[
|
|
703
|
+
...report.decision_summary.confirmed_findings.map((f) => `- [x] **[OBSERVED]** ${sanitizeForMarkdown(f)}`),
|
|
688
704
|
``,
|
|
689
705
|
`### 2. Inferred Opportunities (Modeled Projections)`,
|
|
690
706
|
...report.decision_summary.inferred_opportunities.map((o) => `- [ ] **[OPPORTUNITY]** ${sanitizeForMarkdown(o)}`),
|
|
@@ -703,13 +719,13 @@ export function renderSearchReportMarkdown(report) {
|
|
|
703
719
|
`- **Evidence Link**: \`${p[1].data.evidence_ref}\``,
|
|
704
720
|
`- **Google Organic Indexing**: ${formatVal(p[1].data.google_organic?.indexed_status).toUpperCase()}`,
|
|
705
721
|
`- **Google AI Overviews**: ${formatVal(p[1].data.google_ai_overviews?.inclusion_rate_pct, '%')} inclusion rate (Risk: \`${formatVal(p[1].data.google_ai_overviews?.risk_exposure)}\`)`,
|
|
706
|
-
`- **AI Engine Presence**: Perplexity (\`${
|
|
722
|
+
`- **AI Engine Presence**: Perplexity (\`${scopedFormat(p[1].data.ai_answer_engines?.perplexity)}\`), ChatGPT (\`${scopedFormat(p[1].data.ai_answer_engines?.chatgpt_search)}\`), Copilot (\`${scopedFormat(p[1].data.ai_answer_engines?.copilot)}\`)`,
|
|
707
723
|
``,
|
|
708
724
|
`## 2. Technical Search Infrastructure & Core Web Vitals`,
|
|
709
725
|
`- **Evidence Link**: \`${p[2].data.evidence_ref}\``,
|
|
710
|
-
`- **Crawlability & Canonicals**: ${
|
|
711
|
-
`- **LCP Readiness**: ${
|
|
712
|
-
`- **CLS Readiness**: ${
|
|
726
|
+
`- **Crawlability & Canonicals**: ${scopedFormat(p[2].data.canonical_integrity).toUpperCase()}`,
|
|
727
|
+
`- **LCP Readiness**: ${scopedFormat(p[2].data.core_web_vitals?.lcp_status).toUpperCase()} (${formatVal(p[2].data.core_web_vitals?.render_blocking_scripts)} render-blocking assets)`,
|
|
728
|
+
`- **CLS Readiness**: ${scopedFormat(p[2].data.core_web_vitals?.cls_status).toUpperCase()} (${formatVal(p[2].data.core_web_vitals?.unsized_images)} unsized images)`,
|
|
713
729
|
``,
|
|
714
730
|
`## 3. Organic Performance & Search Intent Analysis`,
|
|
715
731
|
`- **Evidence Link**: \`${p[3].data.evidence_ref}\``,
|
|
@@ -724,7 +740,7 @@ export function renderSearchReportMarkdown(report) {
|
|
|
724
740
|
``,
|
|
725
741
|
`## 5. AEO Readiness Assessment (Answer Engine Optimization)`,
|
|
726
742
|
`- **Evidence Link**: \`${p[5].data.evidence_ref}\``,
|
|
727
|
-
`- **Overall AEO Readiness Score**: **${formatVal(p[5].data.readiness_score)} / 100** (${
|
|
743
|
+
`- **Overall AEO Readiness Score**: **${formatVal(p[5].data.readiness_score)} / 100** (${scopedFormat(p[5].data.status).toUpperCase()})`,
|
|
728
744
|
`- **Direct Answer Density**: ${formatVal(p[5].data.direct_extract_density)}`,
|
|
729
745
|
``,
|
|
730
746
|
`## 6. GEO Assessment (Generative Engine Optimization)`,
|
|
@@ -743,7 +759,7 @@ export function renderSearchReportMarkdown(report) {
|
|
|
743
759
|
`## 8. Entity & Knowledge Graph Integrity`,
|
|
744
760
|
`- **Evidence Link**: \`${p[8].data.evidence_ref}\``,
|
|
745
761
|
`- **Organization Node Resolved**: ${p[8].data.organization_node_resolved ? 'YES' : 'NO'}`,
|
|
746
|
-
`- **External Corroboration**: ${
|
|
762
|
+
`- **External Corroboration**: ${scopedFormat(p[8].data.wikidata_corroboration).toUpperCase()}`,
|
|
747
763
|
``,
|
|
748
764
|
`## 9. Structured-Data & Schema Architecture`,
|
|
749
765
|
`- **Evidence Link**: \`${p[9].data.evidence_ref}\``,
|
|
@@ -753,12 +769,12 @@ export function renderSearchReportMarkdown(report) {
|
|
|
753
769
|
`## 10. Competitive Search Intelligence & Share of Voice`,
|
|
754
770
|
`- **Evidence Link**: \`${p[10].data.evidence_ref}\``,
|
|
755
771
|
`- **1st-Party Citation Share**: **${formatVal(p[10].data.first_party_citation_share_pct, '%')}** vs Competitors (${formatVal(p[10].data.top_competitor_share_pct, '%')})`,
|
|
756
|
-
`- **Primary Gap**: ${
|
|
772
|
+
`- **Primary Gap**: ${scopedFormat(p[10].data.primary_authority_gap)}`,
|
|
757
773
|
``,
|
|
758
774
|
`## 11. Backlink & Off-Page Authority Profile`,
|
|
759
775
|
`- **Evidence Link**: \`${p[11].data.evidence_ref}\``,
|
|
760
776
|
`- **Referring Domains**: ${formatVal(p[11].data.referring_domains)} (Authority Score: ${formatVal(p[11].data.authority_score)}/100)`,
|
|
761
|
-
`- **Toxic Domains**: ${formatVal(p[11].data.toxic_domain_count)} flagged (\`${
|
|
777
|
+
`- **Toxic Domains**: ${formatVal(p[11].data.toxic_domain_count)} flagged (\`${scopedFormat(p[11].data.disavow_recommendation)}\`)`,
|
|
762
778
|
``,
|
|
763
779
|
`## 12. Search Demand & Addressable Market Opportunity`,
|
|
764
780
|
];
|
|
@@ -782,11 +798,11 @@ export function renderSearchReportMarkdown(report) {
|
|
|
782
798
|
`- **Assisted Conversions Share**: ${formatVal(p[13].data.assisted_conversions_share_pct, '%')}`,
|
|
783
799
|
``,
|
|
784
800
|
`## 14. Measurement Integrity & Analytics Instrumentation`,
|
|
785
|
-
`- **GA4 & GSC**: ${formatVal(p[14].data.ga4_instrumentation).toUpperCase()} / ${formatVal(p[14].data.gsc_property_binding).toUpperCase()}`,
|
|
801
|
+
`- **GA4 & GSC**: ${formatVal(scopedValue(p[14].data.ga4_instrumentation)).toUpperCase()} / ${formatVal(scopedValue(p[14].data.gsc_property_binding)).toUpperCase()}`,
|
|
786
802
|
`- **Consent Mode Data Loss**: ${formatVal(p[14].data.consent_mode_v2_loss_pct, '%')}`,
|
|
787
803
|
`- **Limitations**: ${(p[14].data.known_limitations || []).map((l) => sanitizeForMarkdown(l)).join('; ')}`,
|
|
788
804
|
``,
|
|
789
|
-
`## 15.
|
|
805
|
+
`## 15. ${evidenceRegisterTitle}`,
|
|
790
806
|
`| Evidence ID | Source | Methodology | Confidence | Observation Date |`,
|
|
791
807
|
`| :--- | :--- | :--- | :--- | :--- |`,
|
|
792
808
|
...report.evidence_register.map((e) => `| **${e.evidence_id}** | \`${escapeMarkdownTableCell(e.source)}\` | ${escapeMarkdownTableCell((e.methodology || '').slice(0, 50))}... | \`${e.confidence_level}\` | ${e.observation_date} |`),
|
|
@@ -806,15 +822,30 @@ export function renderSearchReportMarkdown(report) {
|
|
|
806
822
|
``
|
|
807
823
|
);
|
|
808
824
|
|
|
809
|
-
|
|
825
|
+
const rendered = lines.join('\n');
|
|
826
|
+
assertEpistemicLanguage(rendered, { ...context, package_verified: context.package_verified === true });
|
|
827
|
+
return rendered;
|
|
810
828
|
}
|
|
811
829
|
|
|
812
830
|
/**
|
|
813
831
|
* Render Search Executive Report as Standalone Enterprise HTML
|
|
814
832
|
*/
|
|
815
|
-
export function renderSearchReportHtml(report) {
|
|
833
|
+
export function renderSearchReportHtml(report, evidenceContext = null) {
|
|
816
834
|
const p = report.pillars;
|
|
817
|
-
|
|
835
|
+
const context = evidenceContext || report.generation_provenance || {};
|
|
836
|
+
const verifiedScope = hasVerifiedEvidenceScope(context);
|
|
837
|
+
const evidenceRegisterTitle = verifiedScope
|
|
838
|
+
? 'Verified Evidence Register'
|
|
839
|
+
: 'Evidence Register (scope-limited; verified status not established)';
|
|
840
|
+
const readinessLabel = verifiedScope ? (report.overall_readiness_score >= 70 ? 'Strong observed posture' : 'Needs optimization') : 'Not established';
|
|
841
|
+
const cwvLabel = verifiedScope && p[2].data.core_web_vitals?.render_blocking_scripts === 0 ? 'No flagged blockers' : 'Not established';
|
|
842
|
+
const scopedFormat = (value, suffix = '') => {
|
|
843
|
+
const rendered = formatVal(value, suffix);
|
|
844
|
+
return !verifiedScope && typeof rendered === 'string' && /\b(?:verified|clean|resolved|optimal|passed)\b|(?:verified_source|clean_profile)/i.test(rendered)
|
|
845
|
+
? 'NOT ESTABLISHED'
|
|
846
|
+
: rendered;
|
|
847
|
+
};
|
|
848
|
+
const rendered = `<!DOCTYPE html>
|
|
818
849
|
<html lang="en">
|
|
819
850
|
<head>
|
|
820
851
|
<meta charset="UTF-8">
|
|
@@ -861,12 +892,12 @@ export function renderSearchReportHtml(report) {
|
|
|
861
892
|
<div class="card">
|
|
862
893
|
<div style="font-size:12px; color:var(--muted); text-transform:uppercase;">Composite Search Readiness</div>
|
|
863
894
|
<div class="card-num">${report.overall_readiness_score} / 100</div>
|
|
864
|
-
<span class="badge ${report.overall_readiness_score >= 70 ? 'badge-success' : 'badge-warning'}">${
|
|
895
|
+
<span class="badge ${verifiedScope && report.overall_readiness_score >= 70 ? 'badge-success' : 'badge-warning'}">${readinessLabel}</span>
|
|
865
896
|
</div>
|
|
866
897
|
<div class="card">
|
|
867
898
|
<div style="font-size:12px; color:var(--muted); text-transform:uppercase;">AEO Direct Extraction</div>
|
|
868
899
|
<div class="card-num">${formatVal(p[5].data.readiness_score)}</div>
|
|
869
|
-
<span class="badge badge-success">${
|
|
900
|
+
<span class="badge badge-success">${scopedFormat(p[5].data.status).toUpperCase()}</span>
|
|
870
901
|
</div>
|
|
871
902
|
<div class="card">
|
|
872
903
|
<div style="font-size:12px; color:var(--muted); text-transform:uppercase;">E-E-A-T Quality Score</div>
|
|
@@ -876,11 +907,11 @@ export function renderSearchReportHtml(report) {
|
|
|
876
907
|
<div class="card">
|
|
877
908
|
<div style="font-size:12px; color:var(--muted); text-transform:uppercase;">Core Web Vitals Blockers</div>
|
|
878
909
|
<div class="card-num">${formatVal(p[2].data.core_web_vitals?.render_blocking_scripts)}</div>
|
|
879
|
-
<span class="badge ${p[2].data.core_web_vitals?.render_blocking_scripts === 0 ? 'badge-success' : 'badge-danger'}">${
|
|
910
|
+
<span class="badge ${verifiedScope && p[2].data.core_web_vitals?.render_blocking_scripts === 0 ? 'badge-success' : 'badge-danger'}">${cwvLabel}</span>
|
|
880
911
|
</div>
|
|
881
912
|
</div>
|
|
882
913
|
|
|
883
|
-
<h2 class="section-title"
|
|
914
|
+
<h2 class="section-title">${evidenceRegisterTitle} (${report.evidence_register.length} Observations)</h2>
|
|
884
915
|
<table>
|
|
885
916
|
<thead>
|
|
886
917
|
<tr>
|
|
@@ -915,6 +946,8 @@ export function renderSearchReportHtml(report) {
|
|
|
915
946
|
</div>
|
|
916
947
|
</body>
|
|
917
948
|
</html>`;
|
|
949
|
+
assertEpistemicLanguage(rendered, { ...context, package_verified: context.package_verified === true });
|
|
950
|
+
return rendered;
|
|
918
951
|
}
|
|
919
952
|
|
|
920
953
|
/**
|
|
@@ -922,15 +955,16 @@ export function renderSearchReportHtml(report) {
|
|
|
922
955
|
*/
|
|
923
956
|
export async function exportExecutiveSearchReport(root, options = {}) {
|
|
924
957
|
const report = await buildExecutiveSearchReport(root, options);
|
|
958
|
+
const evidenceContext = report.generation_provenance;
|
|
925
959
|
const format = options.format || 'markdown';
|
|
926
960
|
let content = '';
|
|
927
961
|
|
|
928
962
|
if (format === 'html' || format === 'html-brief') {
|
|
929
|
-
content = renderSearchReportHtml(report);
|
|
963
|
+
content = renderSearchReportHtml(report, evidenceContext);
|
|
930
964
|
} else if (format === 'json') {
|
|
931
965
|
content = JSON.stringify(report, null, 2);
|
|
932
966
|
} else {
|
|
933
|
-
content = renderSearchReportMarkdown(report);
|
|
967
|
+
content = renderSearchReportMarkdown(report, evidenceContext);
|
|
934
968
|
}
|
|
935
969
|
|
|
936
970
|
let outputPath = null;
|
package/src/reporting/report.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { assertEpistemicLanguage, evidenceScopeStatement } from '../shared/epistemicLanguage.js';
|
|
2
|
+
|
|
1
3
|
const SEV_ORDER = { critical: 0, high: 1, medium: 2, low: 3, informational: 4, experimental: 5 };
|
|
2
4
|
|
|
3
5
|
const RETRIEVAL_NAMESPACES = new Set(['TECH', 'CRAWL', 'LINK', 'HREFLANG']);
|
|
@@ -39,7 +41,7 @@ function citationPosture(promptResults, targetOrigin) {
|
|
|
39
41
|
};
|
|
40
42
|
}
|
|
41
43
|
|
|
42
|
-
export function summarize(findings, { detectorsRun = [], detectorsSkipped = [], promptResults = [], targetOrigin = null } = {}) {
|
|
44
|
+
export function summarize(findings, { detectorsRun = [], detectorsSkipped = [], promptResults = [], targetOrigin = null, coverage = null } = {}) {
|
|
43
45
|
const bySeverity = {};
|
|
44
46
|
const byNamespace = {};
|
|
45
47
|
const byDiscipline = { seo: 0, aeo: 0, geo: 0 };
|
|
@@ -51,6 +53,18 @@ export function summarize(findings, { detectorsRun = [], detectorsSkipped = [],
|
|
|
51
53
|
for (const d of f.discipline) byDiscipline[d] = (byDiscipline[d] || 0) + 1;
|
|
52
54
|
if (f.classification.deterministic) deterministic++;
|
|
53
55
|
}
|
|
56
|
+
const coverageIncomplete = coverage && coverage.coverage_status !== 'complete';
|
|
57
|
+
const posture = {
|
|
58
|
+
retrieval_eligibility: readinessPosture(findings, detectorsRun, detectorsSkipped, RETRIEVAL_NAMESPACES),
|
|
59
|
+
source_extraction_and_support: readinessPosture(findings, detectorsRun, detectorsSkipped, SOURCE_NAMESPACES),
|
|
60
|
+
observed_citation_behavior: citationPosture(promptResults, targetOrigin),
|
|
61
|
+
};
|
|
62
|
+
if (coverageIncomplete) {
|
|
63
|
+
for (const key of ['retrieval_eligibility', 'source_extraction_and_support']) {
|
|
64
|
+
posture[key].result = 'qualified';
|
|
65
|
+
posture[key].coverage_status = coverage.coverage_status;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
54
68
|
return {
|
|
55
69
|
total: findings.length,
|
|
56
70
|
deterministic_observations: deterministic,
|
|
@@ -58,15 +72,22 @@ export function summarize(findings, { detectorsRun = [], detectorsSkipped = [],
|
|
|
58
72
|
by_severity: bySeverity,
|
|
59
73
|
by_namespace: byNamespace,
|
|
60
74
|
by_discipline: byDiscipline,
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
},
|
|
75
|
+
coverage: coverage ? {
|
|
76
|
+
status: coverage.coverage_status,
|
|
77
|
+
populations: coverage.populations,
|
|
78
|
+
stop_reason: coverage.stop_reason,
|
|
79
|
+
} : null,
|
|
80
|
+
posture,
|
|
66
81
|
};
|
|
67
82
|
}
|
|
68
83
|
|
|
69
|
-
export function renderMarkdownReport({ findings, manifest, summary, detectorsSkipped = [] }) {
|
|
84
|
+
export function renderMarkdownReport({ findings, manifest, summary, detectorsSkipped = [], coverage = null, verifiedRun = null }) {
|
|
85
|
+
// Only the verified loader can establish package integrity. Manifest flags
|
|
86
|
+
// are part of the package input and therefore cannot establish their own
|
|
87
|
+
// trustworthiness when this renderer is called directly.
|
|
88
|
+
const packageVerified = verifiedRun?.verified === true
|
|
89
|
+
&& verifiedRun?.integrity_mode === 'sealed'
|
|
90
|
+
&& typeof verifiedRun?.verification_version === 'string';
|
|
70
91
|
const lines = [];
|
|
71
92
|
lines.push(`# Citable audit report`);
|
|
72
93
|
lines.push('');
|
|
@@ -85,10 +106,18 @@ export function renderMarkdownReport({ findings, manifest, summary, detectorsSki
|
|
|
85
106
|
lines.push(`| Total findings | ${summary.total} |`);
|
|
86
107
|
lines.push(`| Deterministic observations | ${summary.deterministic_observations} |`);
|
|
87
108
|
lines.push(`| Heuristic / semantic findings | ${summary.semantic_or_heuristic} |`);
|
|
109
|
+
if (coverage) {
|
|
110
|
+
lines.push(`| Coverage status | ${coverage.coverage_status} |`);
|
|
111
|
+
lines.push(`| Evaluated resources | ${coverage.populations.evaluated} of ${coverage.populations.discovered - coverage.populations.excluded} eligible |`);
|
|
112
|
+
}
|
|
88
113
|
for (const [sev, n] of Object.entries(summary.by_severity).sort((a, b) => SEV_ORDER[a[0]] - SEV_ORDER[b[0]])) {
|
|
89
114
|
lines.push(`| ${sev} | ${n} |`);
|
|
90
115
|
}
|
|
91
116
|
lines.push('');
|
|
117
|
+
if (coverage && coverage.coverage_status !== 'complete') {
|
|
118
|
+
lines.push(`> Coverage status: **${coverage.coverage_status}**. This report describes the observed corpus and does not establish a complete site-wide determination.`);
|
|
119
|
+
lines.push('');
|
|
120
|
+
}
|
|
92
121
|
lines.push('## Separate eligibility and observation states');
|
|
93
122
|
lines.push('');
|
|
94
123
|
lines.push('| Dimension | Result | Evidence |');
|
|
@@ -132,8 +161,26 @@ export function renderMarkdownReport({ findings, manifest, summary, detectorsSki
|
|
|
132
161
|
lines.push('');
|
|
133
162
|
}
|
|
134
163
|
if (findings.length === 0) {
|
|
135
|
-
|
|
164
|
+
const population = coverage?.populations?.evaluated ?? 0;
|
|
165
|
+
lines.push(population > 0
|
|
166
|
+
? `No findings were produced for the ${population} successfully evaluated resource(s). Absence of findings is not proof of eligibility — see skipped and incomplete checks above.`
|
|
167
|
+
: evidenceScopeStatement({
|
|
168
|
+
coverage_status: coverage?.coverage_status || 'indeterminate',
|
|
169
|
+
determination_status: summary?.posture?.source_extraction_and_support?.result === 'pass' ? 'supported' : 'indeterminate',
|
|
170
|
+
evaluated: 0,
|
|
171
|
+
eligible: coverage?.populations?.eligible ?? 0,
|
|
172
|
+
legacy: !coverage,
|
|
173
|
+
}));
|
|
136
174
|
lines.push('');
|
|
137
175
|
}
|
|
138
|
-
|
|
176
|
+
const rendered = lines.join('\n');
|
|
177
|
+
assertEpistemicLanguage(rendered, {
|
|
178
|
+
coverage_status: coverage?.coverage_status || 'indeterminate',
|
|
179
|
+
determination_status: coverage?.coverage_status === 'complete' ? 'supported' : 'indeterminate',
|
|
180
|
+
evaluated: coverage?.populations?.evaluated ?? 0,
|
|
181
|
+
eligible: coverage?.populations?.eligible ?? 0,
|
|
182
|
+
package_verified: packageVerified,
|
|
183
|
+
legacy: verifiedRun?.legacy === true || !coverage,
|
|
184
|
+
});
|
|
185
|
+
return rendered;
|
|
139
186
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers for keeping presentation language within the scope of its evidence.
|
|
3
|
+
*
|
|
4
|
+
* These checks intentionally target assurance phrases, rather than individual
|
|
5
|
+
* words such as `complete` or `verified` which are also legitimate schema and
|
|
6
|
+
* lifecycle states. A report may say that a package is checksum-verified or
|
|
7
|
+
* that a collection completed; it may not turn that fact into an unsupported
|
|
8
|
+
* corpus-wide assurance.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const UNSUPPORTED_ASSURANCE_PATTERNS = Object.freeze([
|
|
12
|
+
/\ball\s+(?:conversion\s+pathways|pages?|resources?|checks?|findings?).{0,40}\b(?:verified|clean|passed|complete)\b/i,
|
|
13
|
+
/\b(?:verified[- ]clean|clean\s+and\s+verified|no\s+issues)\b/i,
|
|
14
|
+
/\b100%\s+(?:clean|verified|complete|pass(?:ed)?|coverage|conversion|funnel)/i,
|
|
15
|
+
/\b(?:site[- ]wide|entire\s+(?:site|corpus|population))\s+(?:clean|verified|complete|resolved|no\s+issues)/i,
|
|
16
|
+
/\b(?:all|entire)\s+(?:site|corpus|population)\s+(?:passed|verified|clean|complete)\b/i,
|
|
17
|
+
/(?:^|\n)\s*(?:verified|clean|resolved|optimal|complete|site[- ]wide|entire|none|all|passed)\b/im,
|
|
18
|
+
/<(?:h[1-6]|span|strong|div)[^>]*>\s*(?:verified|clean|resolved|optimal|complete|site[- ]wide|entire|none|all|passed)\b/i,
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
function normaliseContext(context = {}) {
|
|
22
|
+
const coverage = context.coverage_status || context.coverageStatus || 'indeterminate';
|
|
23
|
+
const determination = context.determination_status || context.determinationStatus || 'indeterminate';
|
|
24
|
+
const evaluated = Number.isInteger(context.evaluated) ? context.evaluated : null;
|
|
25
|
+
const eligible = Number.isInteger(context.eligible) ? context.eligible : null;
|
|
26
|
+
const packageVerified = context.package_verified ?? context.packageVerified ?? false;
|
|
27
|
+
const legacy = context.legacy === true || context.integrity_mode === 'legacy_unverified';
|
|
28
|
+
return { coverage, determination, evaluated, eligible, packageVerified, legacy };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function hasVerifiedEvidenceScope(context = {}) {
|
|
32
|
+
const state = normaliseContext(context);
|
|
33
|
+
return state.packageVerified === true
|
|
34
|
+
&& context.integrity_mode === 'sealed'
|
|
35
|
+
&& !state.legacy
|
|
36
|
+
&& state.coverage === 'complete'
|
|
37
|
+
&& state.determination === 'supported'
|
|
38
|
+
&& Number.isInteger(state.evaluated)
|
|
39
|
+
&& Number.isInteger(state.eligible)
|
|
40
|
+
&& state.evaluated > 0
|
|
41
|
+
&& state.eligible > 0
|
|
42
|
+
&& state.evaluated === state.eligible;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Find assurance phrases that are unsafe for a presentation context.
|
|
47
|
+
* Complete, supported, verified packages may use the phrases when the caller
|
|
48
|
+
* supplies an explicit population; incomplete/legacy/empty evidence may not.
|
|
49
|
+
*/
|
|
50
|
+
export function findEpistemicLanguageViolations(text, context = {}) {
|
|
51
|
+
const value = String(text ?? '');
|
|
52
|
+
const state = normaliseContext(context);
|
|
53
|
+
const cannotClaimCorpus = !hasVerifiedEvidenceScope(context);
|
|
54
|
+
if (!cannotClaimCorpus) return [];
|
|
55
|
+
return UNSUPPORTED_ASSURANCE_PATTERNS
|
|
56
|
+
.map((pattern) => {
|
|
57
|
+
const match = value.match(pattern);
|
|
58
|
+
return match ? { phrase: match[0], index: match.index ?? 0, pattern: pattern.source } : null;
|
|
59
|
+
})
|
|
60
|
+
.filter(Boolean);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function assertEpistemicLanguage(text, context = {}) {
|
|
64
|
+
const violations = findEpistemicLanguageViolations(text, context);
|
|
65
|
+
if (violations.length) {
|
|
66
|
+
throw new Error(`unsupported assurance language: ${violations.map((item) => item.phrase).join('; ')}`);
|
|
67
|
+
}
|
|
68
|
+
return text;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** A short, reusable disclosure for reports and machine-readable exports. */
|
|
72
|
+
export function evidenceScopeStatement(context = {}) {
|
|
73
|
+
const state = normaliseContext(context);
|
|
74
|
+
if (state.evaluated === 0 || state.eligible === 0) {
|
|
75
|
+
return 'No determination is available because no eligible resource was evaluated.';
|
|
76
|
+
}
|
|
77
|
+
if (state.coverage !== 'complete' || state.determination !== 'supported' || state.legacy || !state.packageVerified) {
|
|
78
|
+
const population = state.evaluated == null ? 'the evaluated subset' : `${state.evaluated} evaluated resource(s)`;
|
|
79
|
+
const denominator = state.eligible == null ? '' : ` of ${state.eligible} eligible`;
|
|
80
|
+
return `Coverage is ${state.coverage}; this statement applies only to ${population}${denominator}. Corpus-wide absence or cleanliness is not established.`;
|
|
81
|
+
}
|
|
82
|
+
const population = state.evaluated == null ? 'the evaluated population' : `${state.evaluated} evaluated resource(s)`;
|
|
83
|
+
return `This statement applies to ${population} under the supported evidence contract.`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export const EPISTEMIC_LANGUAGE_PATTERNS = UNSUPPORTED_ASSURANCE_PATTERNS;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { readJson, sha256
|
|
4
|
-
import {
|
|
3
|
+
import { readJson, sha256 } from './io.js';
|
|
4
|
+
import { loadVerifiedRun } from './verifiedRunLoader.js';
|
|
5
5
|
import { buildContext } from '../commands/context.js';
|
|
6
6
|
import { selectDetectors } from '../detectors/index.js';
|
|
7
7
|
import { runDetectors, indexTargets } from '../detectors/framework.js';
|
|
@@ -161,6 +161,12 @@ export async function resolveEvidenceSource(root, options = {}) {
|
|
|
161
161
|
integrity_hash: sha256(findingsJson),
|
|
162
162
|
generation_mode: generationMode,
|
|
163
163
|
run_metadata: null,
|
|
164
|
+
package_verified: false,
|
|
165
|
+
integrity_mode: 'in_memory_unsealed',
|
|
166
|
+
legacy: false,
|
|
167
|
+
coverage: null,
|
|
168
|
+
coverage_status: 'indeterminate',
|
|
169
|
+
determination_status: 'indeterminate',
|
|
164
170
|
};
|
|
165
171
|
}
|
|
166
172
|
|
|
@@ -177,10 +183,14 @@ export async function resolveEvidenceSource(root, options = {}) {
|
|
|
177
183
|
}
|
|
178
184
|
|
|
179
185
|
try {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
186
|
+
// Historical packages are explicitly opened in legacy mode until all
|
|
187
|
+
// producers persist coverage.json. The loader still verifies the sealed
|
|
188
|
+
// package and marks their coverage indeterminate; it never invents a
|
|
189
|
+
// complete corpus.
|
|
190
|
+
const verification = loadVerifiedRun(runPath, {
|
|
191
|
+
requireCompletedExecution: generationMode === 'CONTRACTUAL',
|
|
192
|
+
allowLegacy: true,
|
|
193
|
+
requireCoverage: false,
|
|
184
194
|
});
|
|
185
195
|
|
|
186
196
|
return {
|
|
@@ -188,16 +198,19 @@ export async function resolveEvidenceSource(root, options = {}) {
|
|
|
188
198
|
source_identifier: runId,
|
|
189
199
|
findings: verification.findings,
|
|
190
200
|
findings_count: verification.findingsCount,
|
|
191
|
-
integrity_hash:
|
|
201
|
+
integrity_hash: verification.package_hash,
|
|
192
202
|
generation_mode: generationMode,
|
|
193
203
|
run_metadata: verification.manifest,
|
|
194
204
|
checksums_verified: verification.checksumsVerified,
|
|
205
|
+
package_verified: verification.verified === true && verification.integrity_mode === 'sealed',
|
|
206
|
+
integrity_mode: verification.integrity_mode,
|
|
207
|
+
legacy: verification.legacy === true,
|
|
208
|
+
coverage: verification.coverage,
|
|
209
|
+
coverage_status: verification.coverage_status,
|
|
210
|
+
determination_status: verification.determination_status,
|
|
195
211
|
};
|
|
196
212
|
} catch (err) {
|
|
197
|
-
|
|
198
|
-
throw new FindingsInvalidError(runId, err.message);
|
|
199
|
-
}
|
|
200
|
-
throw err;
|
|
213
|
+
throw new FindingsInvalidError(runId, err.message);
|
|
201
214
|
}
|
|
202
215
|
}
|
|
203
216
|
|
|
@@ -225,6 +238,12 @@ export async function resolveEvidenceSource(root, options = {}) {
|
|
|
225
238
|
generation_mode: generationMode,
|
|
226
239
|
run_metadata: null,
|
|
227
240
|
context: ctx,
|
|
241
|
+
package_verified: false,
|
|
242
|
+
integrity_mode: 'live_unsealed',
|
|
243
|
+
legacy: false,
|
|
244
|
+
coverage: ctx.coverage || null,
|
|
245
|
+
coverage_status: ctx.coverage?.coverage_status || 'indeterminate',
|
|
246
|
+
determination_status: ctx.coverage?.determination_status || 'indeterminate',
|
|
228
247
|
};
|
|
229
248
|
} catch (err) {
|
|
230
249
|
throw new LiveInspectionFailedError(target, err.message);
|
|
@@ -243,12 +262,11 @@ export async function resolveEvidenceSource(root, options = {}) {
|
|
|
243
262
|
const sorted = sortRunCandidatesChronologically(runsDir, candidateDirs);
|
|
244
263
|
for (const candidateId of sorted) {
|
|
245
264
|
const runPath = path.join(runsDir, candidateId);
|
|
246
|
-
const findPath = path.join(runPath, 'findings.json');
|
|
247
265
|
try {
|
|
248
|
-
const verification =
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
266
|
+
const verification = loadVerifiedRun(runPath, {
|
|
267
|
+
requireCompletedExecution: false,
|
|
268
|
+
allowLegacy: true,
|
|
269
|
+
requireCoverage: false,
|
|
252
270
|
});
|
|
253
271
|
|
|
254
272
|
return {
|
|
@@ -256,9 +274,16 @@ export async function resolveEvidenceSource(root, options = {}) {
|
|
|
256
274
|
source_identifier: candidateId,
|
|
257
275
|
findings: verification.findings,
|
|
258
276
|
findings_count: verification.findingsCount,
|
|
259
|
-
integrity_hash:
|
|
277
|
+
integrity_hash: verification.package_hash,
|
|
260
278
|
generation_mode: generationMode,
|
|
261
279
|
run_metadata: verification.manifest,
|
|
280
|
+
checksums_verified: verification.checksumsVerified,
|
|
281
|
+
package_verified: verification.verified === true && verification.integrity_mode === 'sealed',
|
|
282
|
+
integrity_mode: verification.integrity_mode,
|
|
283
|
+
legacy: verification.legacy === true,
|
|
284
|
+
coverage: verification.coverage,
|
|
285
|
+
coverage_status: verification.coverage_status,
|
|
286
|
+
determination_status: verification.determination_status,
|
|
262
287
|
};
|
|
263
288
|
} catch {
|
|
264
289
|
// Try next candidate
|
|
@@ -278,6 +303,12 @@ export async function resolveEvidenceSource(root, options = {}) {
|
|
|
278
303
|
integrity_hash: null,
|
|
279
304
|
generation_mode: 'NON_CONTRACTUAL_SAMPLE',
|
|
280
305
|
run_metadata: null,
|
|
306
|
+
package_verified: false,
|
|
307
|
+
integrity_mode: 'sample_unverified',
|
|
308
|
+
legacy: false,
|
|
309
|
+
coverage: null,
|
|
310
|
+
coverage_status: 'indeterminate',
|
|
311
|
+
determination_status: 'indeterminate',
|
|
281
312
|
};
|
|
282
313
|
}
|
|
283
314
|
|
package/src/shared/htmlEscape.js
CHANGED
|
@@ -26,7 +26,7 @@ export function escapeHtmlAttr(str) {
|
|
|
26
26
|
return escapeHtml(str);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
const SAFE_PROTOCOLS = new Set(['http:', 'https:', 'mailto:']);
|
|
29
|
+
const SAFE_PROTOCOLS = new Set(['http:', 'https:', 'mailto:', 'ftp:']);
|
|
30
30
|
|
|
31
31
|
export function sanitizeUrl(urlStr, fallback = '#') {
|
|
32
32
|
if (!urlStr || typeof urlStr !== 'string') return fallback;
|
|
@@ -38,11 +38,15 @@ export function sanitizeUrl(urlStr, fallback = '#') {
|
|
|
38
38
|
try {
|
|
39
39
|
const parsed = new URL(trimmed);
|
|
40
40
|
if (SAFE_PROTOCOLS.has(parsed.protocol)) {
|
|
41
|
-
|
|
41
|
+
// Additional substring sanitization: ensure no CRLF injection via encoded newlines
|
|
42
|
+
const normalized = parsed.href.replace(/\r\n|\r/g, '\n');
|
|
43
|
+
return escapeHtmlAttr(normalized);
|
|
42
44
|
}
|
|
43
|
-
return fallback
|
|
45
|
+
// Unsafe protocol: return fully sanitized fallback
|
|
46
|
+
return escapeHtmlAttr(fallback);
|
|
44
47
|
} catch {
|
|
45
|
-
return fallback
|
|
48
|
+
// Invalid URL: return sanitized fallback
|
|
49
|
+
return escapeHtmlAttr(fallback);
|
|
46
50
|
}
|
|
47
51
|
}
|
|
48
52
|
|