@nebulacomponents/citable 1.18.1 → 1.19.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 +76 -0
- package/dist/universal/.agents/skills/citable/SKILL.md +1 -1
- package/dist/universal/.agents/skills/citable/VERSION +1 -1
- package/dist/universal/.agents/skills/citable/commands/artifact-interchange.md +5 -0
- package/dist/universal/.agents/skills/citable/commands/remediation.md +1 -1
- package/dist/universal/.agents/skills/citable/commands/sow.md +2 -2
- package/dist/universal/.agents/skills/citable/manifest.json +15 -10
- 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/cro-report.schema.json +112 -0
- package/dist/universal/.agents/skills/citable/schemas/search-report.schema.json +99 -0
- package/dist/universal/.agents/skills/citable/schemas/sow.schema.json +3 -2
- package/dist/universal/.claude/agents/citable/manifest.json +2 -2
- package/dist/universal/.claude/skills/citable/SKILL.md +1 -1
- package/dist/universal/.claude/skills/citable/VERSION +1 -1
- package/dist/universal/.claude/skills/citable/commands/artifact-interchange.md +5 -0
- package/dist/universal/.claude/skills/citable/commands/remediation.md +1 -1
- package/dist/universal/.claude/skills/citable/commands/sow.md +2 -2
- package/dist/universal/.claude/skills/citable/manifest.json +15 -10
- 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/cro-report.schema.json +112 -0
- package/dist/universal/.claude/skills/citable/schemas/search-report.schema.json +99 -0
- package/dist/universal/.claude/skills/citable/schemas/sow.schema.json +3 -2
- package/dist/universal/.cursor/skills/citable/SKILL.md +1 -1
- package/dist/universal/.cursor/skills/citable/VERSION +1 -1
- package/dist/universal/.cursor/skills/citable/commands/artifact-interchange.md +5 -0
- package/dist/universal/.cursor/skills/citable/commands/remediation.md +1 -1
- package/dist/universal/.cursor/skills/citable/commands/sow.md +2 -2
- package/dist/universal/.cursor/skills/citable/manifest.json +15 -10
- 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/cro-report.schema.json +112 -0
- package/dist/universal/.cursor/skills/citable/schemas/search-report.schema.json +99 -0
- package/dist/universal/.cursor/skills/citable/schemas/sow.schema.json +3 -2
- package/dist/universal/.gemini/skills/citable/SKILL.md +1 -1
- package/dist/universal/.gemini/skills/citable/VERSION +1 -1
- package/dist/universal/.gemini/skills/citable/commands/artifact-interchange.md +5 -0
- package/dist/universal/.gemini/skills/citable/commands/remediation.md +1 -1
- package/dist/universal/.gemini/skills/citable/commands/sow.md +2 -2
- package/dist/universal/.gemini/skills/citable/manifest.json +15 -10
- 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/cro-report.schema.json +112 -0
- package/dist/universal/.gemini/skills/citable/schemas/search-report.schema.json +99 -0
- package/dist/universal/.gemini/skills/citable/schemas/sow.schema.json +3 -2
- package/dist/universal/.github/skills/citable/SKILL.md +1 -1
- package/dist/universal/.github/skills/citable/VERSION +1 -1
- package/dist/universal/.github/skills/citable/commands/artifact-interchange.md +5 -0
- package/dist/universal/.github/skills/citable/commands/remediation.md +1 -1
- package/dist/universal/.github/skills/citable/commands/sow.md +2 -2
- package/dist/universal/.github/skills/citable/manifest.json +15 -10
- 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/cro-report.schema.json +112 -0
- package/dist/universal/.github/skills/citable/schemas/search-report.schema.json +99 -0
- package/dist/universal/.github/skills/citable/schemas/sow.schema.json +3 -2
- package/dist/universal/.kiro/skills/citable/SKILL.md +1 -1
- package/dist/universal/.kiro/skills/citable/VERSION +1 -1
- package/dist/universal/.kiro/skills/citable/commands/artifact-interchange.md +5 -0
- package/dist/universal/.kiro/skills/citable/commands/remediation.md +1 -1
- package/dist/universal/.kiro/skills/citable/commands/sow.md +2 -2
- package/dist/universal/.kiro/skills/citable/manifest.json +15 -10
- 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/cro-report.schema.json +112 -0
- package/dist/universal/.kiro/skills/citable/schemas/search-report.schema.json +99 -0
- package/dist/universal/.kiro/skills/citable/schemas/sow.schema.json +3 -2
- package/dist/universal/.opencode/skills/citable/SKILL.md +1 -1
- package/dist/universal/.opencode/skills/citable/VERSION +1 -1
- package/dist/universal/.opencode/skills/citable/commands/artifact-interchange.md +5 -0
- package/dist/universal/.opencode/skills/citable/commands/remediation.md +1 -1
- package/dist/universal/.opencode/skills/citable/commands/sow.md +2 -2
- package/dist/universal/.opencode/skills/citable/manifest.json +15 -10
- 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/cro-report.schema.json +112 -0
- package/dist/universal/.opencode/skills/citable/schemas/search-report.schema.json +99 -0
- package/dist/universal/.opencode/skills/citable/schemas/sow.schema.json +3 -2
- package/dist/universal/.pi/agent/skills/citable/SKILL.md +1 -1
- package/dist/universal/.pi/agent/skills/citable/VERSION +1 -1
- package/dist/universal/.pi/agent/skills/citable/commands/artifact-interchange.md +5 -0
- package/dist/universal/.pi/agent/skills/citable/commands/remediation.md +1 -1
- package/dist/universal/.pi/agent/skills/citable/commands/sow.md +2 -2
- package/dist/universal/.pi/agent/skills/citable/manifest.json +15 -10
- 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/cro-report.schema.json +112 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/search-report.schema.json +99 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/sow.schema.json +3 -2
- package/dist/universal/.qoder/skills/citable/SKILL.md +1 -1
- package/dist/universal/.qoder/skills/citable/VERSION +1 -1
- package/dist/universal/.qoder/skills/citable/commands/artifact-interchange.md +5 -0
- package/dist/universal/.qoder/skills/citable/commands/remediation.md +1 -1
- package/dist/universal/.qoder/skills/citable/commands/sow.md +2 -2
- package/dist/universal/.qoder/skills/citable/manifest.json +15 -10
- 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/cro-report.schema.json +112 -0
- package/dist/universal/.qoder/skills/citable/schemas/search-report.schema.json +99 -0
- package/dist/universal/.qoder/skills/citable/schemas/sow.schema.json +3 -2
- package/dist/universal/.rovodev/skills/citable/SKILL.md +1 -1
- package/dist/universal/.rovodev/skills/citable/VERSION +1 -1
- package/dist/universal/.rovodev/skills/citable/commands/artifact-interchange.md +5 -0
- package/dist/universal/.rovodev/skills/citable/commands/remediation.md +1 -1
- package/dist/universal/.rovodev/skills/citable/commands/sow.md +2 -2
- package/dist/universal/.rovodev/skills/citable/manifest.json +15 -10
- 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/cro-report.schema.json +112 -0
- package/dist/universal/.rovodev/skills/citable/schemas/search-report.schema.json +99 -0
- package/dist/universal/.rovodev/skills/citable/schemas/sow.schema.json +3 -2
- package/dist/universal/.trae/skills/citable/SKILL.md +1 -1
- package/dist/universal/.trae/skills/citable/VERSION +1 -1
- package/dist/universal/.trae/skills/citable/commands/artifact-interchange.md +5 -0
- package/dist/universal/.trae/skills/citable/commands/remediation.md +1 -1
- package/dist/universal/.trae/skills/citable/commands/sow.md +2 -2
- package/dist/universal/.trae/skills/citable/manifest.json +15 -10
- 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/cro-report.schema.json +112 -0
- package/dist/universal/.trae/skills/citable/schemas/search-report.schema.json +99 -0
- package/dist/universal/.trae/skills/citable/schemas/sow.schema.json +3 -2
- package/dist/universal/.trae-cn/skills/citable/SKILL.md +1 -1
- package/dist/universal/.trae-cn/skills/citable/VERSION +1 -1
- package/dist/universal/.trae-cn/skills/citable/commands/artifact-interchange.md +5 -0
- package/dist/universal/.trae-cn/skills/citable/commands/remediation.md +1 -1
- package/dist/universal/.trae-cn/skills/citable/commands/sow.md +2 -2
- package/dist/universal/.trae-cn/skills/citable/manifest.json +15 -10
- 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/cro-report.schema.json +112 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/search-report.schema.json +99 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/sow.schema.json +3 -2
- package/dist/universal/manifest.json +184 -124
- package/package.json +2 -1
- 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/cro-report.schema.json +112 -0
- package/schemas/search-report.schema.json +99 -0
- package/schemas/sow.schema.json +3 -2
- package/skill/SKILL.md +1 -1
- package/skill/commands/artifact-interchange.md +5 -0
- package/skill/commands/remediation.md +1 -1
- package/skill/commands/sow.md +2 -2
- package/src/analysis/behavioral.js +5 -4
- package/src/analysis/eeat.js +10 -9
- package/src/analysis/offpage.js +19 -36
- package/src/analysis/readiness.js +8 -2
- package/src/artifacts/verifyCustomerArtifact.js +283 -0
- package/src/cli/index.js +8 -1
- 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 +393 -229
- package/src/reporting/executiveExport.js +6 -2
- package/src/reporting/executiveSearchReport.js +519 -271
- package/src/shared/domainUtils.js +140 -0
- package/src/shared/epistemicStatus.js +111 -0
- package/src/shared/evidenceSourceResolver.js +286 -0
- package/src/shared/htmlEscape.js +65 -0
- package/src/shared/runPackageVerifier.js +153 -0
- package/src/sow/admissibilityGate.js +20 -2
- package/src/sow/generateSow.js +125 -159
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { readJson } from '../shared/io.js';
|
|
4
|
+
import { validateAgainst } from '../shared/schemaValidator.js';
|
|
5
|
+
|
|
6
|
+
export class CustomerArtifactVerificationError extends Error {
|
|
7
|
+
constructor(message, errors = []) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = 'CustomerArtifactVerificationError';
|
|
10
|
+
this.errors = errors;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const DANGEROUS_PATTERNS = [
|
|
15
|
+
/<script\b[^>]*>/i,
|
|
16
|
+
/javascript\s*:/i,
|
|
17
|
+
/on(?:load|error|click|mouseover|focus|blur|submit)\s*=/i,
|
|
18
|
+
/<iframe\b[^>]*>/i,
|
|
19
|
+
/<object\b[^>]*>/i,
|
|
20
|
+
/<embed\b[^>]*>/i,
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
function scanForInjections(value, pathPrefix = '', found = []) {
|
|
24
|
+
if (typeof value === 'string') {
|
|
25
|
+
for (const pattern of DANGEROUS_PATTERNS) {
|
|
26
|
+
if (pattern.test(value)) {
|
|
27
|
+
found.push(`Potential unescaped script/event injection at ${pathPrefix}: "${value.slice(0, 80)}"`);
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
} else if (Array.isArray(value)) {
|
|
32
|
+
for (let i = 0; i < value.length; i++) {
|
|
33
|
+
scanForInjections(value[i], `${pathPrefix}[${i}]`, found);
|
|
34
|
+
}
|
|
35
|
+
} else if (value && typeof value === 'object') {
|
|
36
|
+
for (const [k, v] of Object.entries(value)) {
|
|
37
|
+
scanForInjections(v, pathPrefix ? `${pathPrefix}.${k}` : k, found);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return found;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Identify artifact type and validate customer artifact against schema,
|
|
45
|
+
* epistemic invariants, arithmetic integrity, and injection hazards.
|
|
46
|
+
*/
|
|
47
|
+
export async function verifyCustomerArtifact(artifactPathOrData, options = {}) {
|
|
48
|
+
const { failClosed = false, contractual = false } = options;
|
|
49
|
+
const errors = [];
|
|
50
|
+
const warnings = [];
|
|
51
|
+
|
|
52
|
+
let rawContent = null;
|
|
53
|
+
let data = null;
|
|
54
|
+
let format = 'json';
|
|
55
|
+
let artifactPath = null;
|
|
56
|
+
|
|
57
|
+
if (typeof artifactPathOrData === 'string') {
|
|
58
|
+
if (fs.existsSync(artifactPathOrData)) {
|
|
59
|
+
artifactPath = artifactPathOrData;
|
|
60
|
+
rawContent = fs.readFileSync(artifactPathOrData, 'utf8');
|
|
61
|
+
const ext = path.extname(artifactPathOrData).toLowerCase();
|
|
62
|
+
if (ext === '.html' || ext === '.htm') {
|
|
63
|
+
format = 'html';
|
|
64
|
+
} else if (ext === '.md' || ext === '.markdown') {
|
|
65
|
+
format = 'markdown';
|
|
66
|
+
} else {
|
|
67
|
+
try {
|
|
68
|
+
data = JSON.parse(rawContent);
|
|
69
|
+
format = 'json';
|
|
70
|
+
} catch (e) {
|
|
71
|
+
errors.push(`Failed to parse JSON file ${artifactPathOrData}: ${e.message}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
} else {
|
|
75
|
+
// String content passed directly
|
|
76
|
+
rawContent = artifactPathOrData;
|
|
77
|
+
if (rawContent.trim().startsWith('{')) {
|
|
78
|
+
try {
|
|
79
|
+
data = JSON.parse(rawContent);
|
|
80
|
+
format = 'json';
|
|
81
|
+
} catch {
|
|
82
|
+
format = 'text';
|
|
83
|
+
}
|
|
84
|
+
} else if (rawContent.includes('<!DOCTYPE html>') || rawContent.includes('<html')) {
|
|
85
|
+
format = 'html';
|
|
86
|
+
} else {
|
|
87
|
+
format = 'markdown';
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
} else if (typeof artifactPathOrData === 'object' && artifactPathOrData !== null) {
|
|
91
|
+
data = artifactPathOrData;
|
|
92
|
+
format = 'json';
|
|
93
|
+
} else {
|
|
94
|
+
errors.push('Invalid artifact input: must be file path, string content, or JSON object');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (errors.length > 0) {
|
|
98
|
+
if (failClosed) throw new CustomerArtifactVerificationError(errors.join('; '), errors);
|
|
99
|
+
return { valid: false, artifact_type: 'UNKNOWN', format, errors, warnings };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let artifactType = 'UNKNOWN';
|
|
103
|
+
|
|
104
|
+
// 1. JSON Verification
|
|
105
|
+
if (format === 'json' && data) {
|
|
106
|
+
// Detect artifact type
|
|
107
|
+
if (data.sow_id || data.traceability_matrix || (data.$schema && data.$schema.includes('sow'))) {
|
|
108
|
+
artifactType = 'SOW';
|
|
109
|
+
} else if (data.fact_status === 'enterprise_search_executive_report' || data.overall_visibility_score !== undefined) {
|
|
110
|
+
artifactType = 'SEARCH_REPORT';
|
|
111
|
+
} else if (data.fact_status === 'enterprise_cro_executive_report' || data.overall_conversion_readiness !== undefined) {
|
|
112
|
+
artifactType = 'CRO_REPORT';
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Schema validation
|
|
116
|
+
if (artifactType === 'SOW') {
|
|
117
|
+
const val = validateAgainst('sow.schema.json', data);
|
|
118
|
+
if (!val.valid) {
|
|
119
|
+
errors.push(...val.errors.map((e) => `SOW schema violation: ${e}`));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Check commercial arithmetic
|
|
123
|
+
if (typeof data.commercial_total_fee_minor === 'number' && typeof data.commercial_total_fee_usd === 'number') {
|
|
124
|
+
if (data.commercial_total_fee_minor !== Math.round(data.commercial_total_fee_usd * 100)) {
|
|
125
|
+
errors.push(`SOW commercial fee mismatch: minor ${data.commercial_total_fee_minor} != usd ${data.commercial_total_fee_usd}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Milestones check
|
|
130
|
+
if (data.delivery_schedule?.milestones) {
|
|
131
|
+
const minorSum = data.delivery_schedule.milestones.reduce((acc, m) => acc + (m.fee_minor || 0), 0);
|
|
132
|
+
if (data.commercial_total_fee_minor !== undefined && minorSum !== data.commercial_total_fee_minor) {
|
|
133
|
+
errors.push(`SOW milestone fees sum (${minorSum}) does not match total fee (${data.commercial_total_fee_minor})`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Admissibility gate balance
|
|
138
|
+
if (data.admissibility_gate) {
|
|
139
|
+
const { total_findings_evaluated, admitted_count, refused_count } = data.admissibility_gate;
|
|
140
|
+
if (total_findings_evaluated !== admitted_count + refused_count) {
|
|
141
|
+
errors.push(`SOW admissibility numbers do not balance: ${total_findings_evaluated} != ${admitted_count} + ${refused_count}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Contractual mode checks
|
|
146
|
+
const isContractual = data.generation_mode === 'CONTRACTUAL' || contractual;
|
|
147
|
+
if (isContractual) {
|
|
148
|
+
if (data.synthetic_evidence) {
|
|
149
|
+
errors.push('Contractual SOW cannot contain synthetic_evidence');
|
|
150
|
+
}
|
|
151
|
+
if (data.traceability_matrix) {
|
|
152
|
+
for (const row of data.traceability_matrix) {
|
|
153
|
+
if (row.owner_source === 'built_in_template_default') {
|
|
154
|
+
errors.push(`Contractual SOW requirement ${row.sow_requirement_id} relies on unverified built_in_template_default owner`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Injection scan on user fields
|
|
161
|
+
const injections = scanForInjections({
|
|
162
|
+
client: data.client,
|
|
163
|
+
title: data.title,
|
|
164
|
+
traceability_matrix: data.traceability_matrix,
|
|
165
|
+
refusal_log: data.admissibility_gate?.refusal_log,
|
|
166
|
+
});
|
|
167
|
+
errors.push(...injections);
|
|
168
|
+
|
|
169
|
+
} else if (artifactType === 'SEARCH_REPORT') {
|
|
170
|
+
const val = validateAgainst('search-report.schema.json', data);
|
|
171
|
+
if (!val.valid) {
|
|
172
|
+
errors.push(...val.errors.map((e) => `Search report schema violation: ${e}`));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (data.pillars && Object.keys(data.pillars).length < 19) {
|
|
176
|
+
errors.push(`Search report must contain all 19 enterprise pillars, found ${Object.keys(data.pillars).length}`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Scan for dangerous injection in titles, client, target
|
|
180
|
+
const injections = scanForInjections({
|
|
181
|
+
client_name: data.client_name,
|
|
182
|
+
target_domain: data.target_domain,
|
|
183
|
+
decision_summary: data.decision_summary,
|
|
184
|
+
});
|
|
185
|
+
errors.push(...injections);
|
|
186
|
+
|
|
187
|
+
} else if (artifactType === 'CRO_REPORT') {
|
|
188
|
+
const val = validateAgainst('cro-report.schema.json', data);
|
|
189
|
+
if (!val.valid) {
|
|
190
|
+
errors.push(...val.errors.map((e) => `CRO report schema violation: ${e}`));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (data.pillars && Object.keys(data.pillars).length < 25) {
|
|
194
|
+
errors.push(`CRO report must contain all 25 enterprise pillars, found ${Object.keys(data.pillars).length}`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Scan for injection
|
|
198
|
+
const injections = scanForInjections({
|
|
199
|
+
client_name: data.client_name,
|
|
200
|
+
target_domain: data.target_domain,
|
|
201
|
+
decision_summary: data.decision_summary,
|
|
202
|
+
});
|
|
203
|
+
errors.push(...injections);
|
|
204
|
+
|
|
205
|
+
} else {
|
|
206
|
+
errors.push('Unrecognized customer artifact structure');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
} else if (format === 'html' || format === 'markdown') {
|
|
210
|
+
// HTML or Markdown scan
|
|
211
|
+
const content = rawContent || '';
|
|
212
|
+
|
|
213
|
+
// Check for malicious unescaped scripts
|
|
214
|
+
for (const pattern of DANGEROUS_PATTERNS) {
|
|
215
|
+
if (pattern.test(content)) {
|
|
216
|
+
errors.push(`Artifact contains forbidden unescaped script or event handler: ${pattern}`);
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Check governance disclaimers
|
|
222
|
+
if (content.includes('Statement of Work') || content.includes('SOW-')) {
|
|
223
|
+
artifactType = 'SOW';
|
|
224
|
+
if (!content.includes('guaranteed') && !content.includes('traceability')) {
|
|
225
|
+
warnings.push('SOW document lacks standard non-guarantee governance disclosure');
|
|
226
|
+
}
|
|
227
|
+
} else if (content.includes('Conversion Rate Optimization') || content.includes('CRO')) {
|
|
228
|
+
artifactType = 'CRO_REPORT';
|
|
229
|
+
if (!content.includes('guaranteed') && !content.includes('Scientific Epistemology')) {
|
|
230
|
+
warnings.push('CRO report lacks standard scientific epistemology disclaimer');
|
|
231
|
+
}
|
|
232
|
+
} else if (content.includes('Search Intelligence') || content.includes('SEO')) {
|
|
233
|
+
artifactType = 'SEARCH_REPORT';
|
|
234
|
+
if (!content.includes('guaranteed') && !content.includes('Governance Notice')) {
|
|
235
|
+
warnings.push('Search report lacks standard non-guarantee disclosure');
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const valid = errors.length === 0;
|
|
241
|
+
if (!valid && failClosed) {
|
|
242
|
+
throw new CustomerArtifactVerificationError(`Artifact verification failed: ${errors.join('; ')}`, errors);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
valid,
|
|
247
|
+
artifact_type: artifactType,
|
|
248
|
+
format,
|
|
249
|
+
generation_mode: data?.generation_mode || data?.generation_provenance?.generation_mode || 'UNKNOWN',
|
|
250
|
+
errors,
|
|
251
|
+
warnings,
|
|
252
|
+
provenance: data?.generation_provenance || null,
|
|
253
|
+
summary: valid
|
|
254
|
+
? `Artifact verified successfully (${artifactType} - ${format})`
|
|
255
|
+
: `Artifact verification failed (${artifactType} - ${format}): ${errors.length} error(s)`,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* CLI command entry point
|
|
261
|
+
*/
|
|
262
|
+
export async function verifyCustomerArtifactCommand(filePath, args = [], root = process.cwd()) {
|
|
263
|
+
const fullPath = path.isAbsolute(filePath) ? filePath : path.resolve(root, filePath);
|
|
264
|
+
if (!fs.existsSync(fullPath)) {
|
|
265
|
+
return {
|
|
266
|
+
command: 'artifacts verify-customer',
|
|
267
|
+
file: filePath,
|
|
268
|
+
valid: false,
|
|
269
|
+
errors: [`File not found: ${fullPath}`],
|
|
270
|
+
message: `Artifact verification error: file not found: ${fullPath}`,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const result = await verifyCustomerArtifact(fullPath);
|
|
275
|
+
return {
|
|
276
|
+
command: 'artifacts verify-customer',
|
|
277
|
+
file: filePath,
|
|
278
|
+
...result,
|
|
279
|
+
message: result.valid
|
|
280
|
+
? `SUCCESS: Artifact ${filePath} verified successfully (${result.artifact_type}, format: ${result.format}).`
|
|
281
|
+
: `FAILED: Artifact ${filePath} failed verification:\n${result.errors.map((e) => ` - ${e}`).join('\n')}`,
|
|
282
|
+
};
|
|
283
|
+
}
|
package/src/cli/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import { auditBacklinks, formatBacklinksOutput } from "../commands/auditBacklink
|
|
|
9
9
|
import { roadmapCommand } from "../commands/roadmapCmd.js";
|
|
10
10
|
import { sowCommand } from "../commands/sowCmd.js";
|
|
11
11
|
import { buildIceMatrix, formatIceMatrixOutput } from "../analysis/iceMatrix.js";
|
|
12
|
+
import { verifyCustomerArtifactCommand } from "../artifacts/verifyCustomerArtifact.js";
|
|
12
13
|
import fs from 'node:fs';
|
|
13
14
|
import path from 'node:path';
|
|
14
15
|
import { pathToFileURL } from 'node:url';
|
|
@@ -1117,7 +1118,13 @@ ${r.per_detector.filter((d) => d.true_positives + d.false_negatives + d.false_po
|
|
|
1117
1118
|
} else if (mode === 'import') {
|
|
1118
1119
|
const r = importArtifactPackage(root, { input: args.input });
|
|
1119
1120
|
out(args, `artifacts import ${r.run_id}: ${r.status}\nRun package: ${r.destination}`, r);
|
|
1120
|
-
} else
|
|
1121
|
+
} else if (mode === 'verify-customer') {
|
|
1122
|
+
const filePath = args._[1];
|
|
1123
|
+
if (!filePath) throw new Error('usage: citable artifacts verify-customer <file>');
|
|
1124
|
+
const r = await verifyCustomerArtifactCommand(filePath, argv.slice(3), root);
|
|
1125
|
+
out(args, r.message, r);
|
|
1126
|
+
if (!r.valid) process.exitCode = 1;
|
|
1127
|
+
} else throw new Error('usage: citable artifacts <export <run-id> --output <directory>|verify --input <directory>|import --input <directory>|verify-customer <file>>');
|
|
1121
1128
|
break;
|
|
1122
1129
|
}
|
|
1123
1130
|
case 'verify': {
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { nowIso } from '../../shared/io.js';
|
|
2
|
+
|
|
3
|
+
export class CorrelationTracker {
|
|
4
|
+
constructor() {
|
|
5
|
+
this._sequence = 0;
|
|
6
|
+
this._startTime = performance.now();
|
|
7
|
+
this._currentStep = null; // { step_id, step_sequence, checkpoint_id }
|
|
8
|
+
this._phase = 'initial'; // 'initial' | 'during_step' | 'between_steps' | 'post_journey'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
nextSequence() {
|
|
12
|
+
this._sequence += 1;
|
|
13
|
+
return this._sequence;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
timing() {
|
|
17
|
+
const elapsed = Math.round((performance.now() - this._startTime) * 100) / 100;
|
|
18
|
+
return {
|
|
19
|
+
monotonic_offset_ms: elapsed,
|
|
20
|
+
timestamp: nowIso(),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
startStep(step, stepSequence) {
|
|
25
|
+
this._currentStep = {
|
|
26
|
+
step_id: step.step_id,
|
|
27
|
+
step_sequence: stepSequence,
|
|
28
|
+
checkpoint_id: step.checkpoint_id || null,
|
|
29
|
+
};
|
|
30
|
+
this._phase = 'during_step';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
endStep(step, stepSequence) {
|
|
34
|
+
this._phase = 'between_steps';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
finishJourney() {
|
|
38
|
+
this._phase = 'post_journey';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get correlation context for a newly initiated event/request.
|
|
43
|
+
*/
|
|
44
|
+
getCorrelation(checkpointId = null) {
|
|
45
|
+
const stepId = this._currentStep ? this._currentStep.step_id : null;
|
|
46
|
+
return {
|
|
47
|
+
step_id: stepId,
|
|
48
|
+
step_sequence: this._currentStep ? this._currentStep.step_sequence : null,
|
|
49
|
+
checkpoint_id: checkpointId || (this._currentStep ? this._currentStep.checkpoint_id : null),
|
|
50
|
+
attribution_phase: this._phase,
|
|
51
|
+
initiated_step_id: stepId,
|
|
52
|
+
completed_step_id: null,
|
|
53
|
+
ambiguous_async: false,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Correlate completion of an asynchronous transaction (e.g. HTTP response).
|
|
59
|
+
* If the initiating step differs from the completing step/phase, mark as ambiguous_async.
|
|
60
|
+
*/
|
|
61
|
+
correlateCompletion(initiatedCorrelation) {
|
|
62
|
+
const currentCorrelation = this.getCorrelation();
|
|
63
|
+
const isSameStep = initiatedCorrelation.step_id === currentCorrelation.step_id;
|
|
64
|
+
const isSamePhase = initiatedCorrelation.attribution_phase === currentCorrelation.attribution_phase;
|
|
65
|
+
|
|
66
|
+
let phase = initiatedCorrelation.attribution_phase;
|
|
67
|
+
let ambiguousAsync = false;
|
|
68
|
+
if (!isSameStep || (!isSamePhase && currentCorrelation.attribution_phase !== 'during_step')) {
|
|
69
|
+
phase = 'ambiguous_async';
|
|
70
|
+
ambiguousAsync = true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
step_id: initiatedCorrelation.step_id,
|
|
75
|
+
step_sequence: initiatedCorrelation.step_sequence,
|
|
76
|
+
checkpoint_id: initiatedCorrelation.checkpoint_id,
|
|
77
|
+
attribution_phase: phase,
|
|
78
|
+
initiated_step_id: initiatedCorrelation.step_id,
|
|
79
|
+
completed_step_id: currentCorrelation.step_id,
|
|
80
|
+
ambiguous_async: ambiguousAsync,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|