@nebulacomponents/citable 1.17.0 → 1.18.1
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 +62 -0
- package/dist/universal/.agents/skills/citable/SKILL.md +41 -1
- package/dist/universal/.agents/skills/citable/VERSION +1 -1
- package/dist/universal/.agents/skills/citable/agents/citable-remediator.md +43 -0
- package/dist/universal/.agents/skills/citable/agents/citable-sow-architect.md +43 -0
- package/dist/universal/.agents/skills/citable/commands/README.md +13 -0
- package/dist/universal/.agents/skills/citable/commands/remediation.md +55 -0
- package/dist/universal/.agents/skills/citable/commands/reporting.md +28 -0
- package/dist/universal/.agents/skills/citable/commands/sow.md +95 -0
- package/dist/universal/.agents/skills/citable/manifest.json +12 -7
- package/dist/universal/.agents/skills/citable/schemas/sow.schema.json +409 -0
- package/dist/universal/.claude/agents/citable/citable-remediator.md +43 -0
- package/dist/universal/.claude/agents/citable/citable-sow-architect.md +43 -0
- package/dist/universal/.claude/agents/citable/manifest.json +6 -4
- package/dist/universal/.claude/skills/citable/SKILL.md +41 -1
- package/dist/universal/.claude/skills/citable/VERSION +1 -1
- package/dist/universal/.claude/skills/citable/agents/citable-remediator.md +43 -0
- package/dist/universal/.claude/skills/citable/agents/citable-sow-architect.md +43 -0
- package/dist/universal/.claude/skills/citable/commands/README.md +13 -0
- package/dist/universal/.claude/skills/citable/commands/remediation.md +55 -0
- package/dist/universal/.claude/skills/citable/commands/reporting.md +28 -0
- package/dist/universal/.claude/skills/citable/commands/sow.md +95 -0
- package/dist/universal/.claude/skills/citable/manifest.json +16 -9
- package/dist/universal/.claude/skills/citable/schemas/sow.schema.json +409 -0
- package/dist/universal/.cursor/skills/citable/SKILL.md +41 -1
- package/dist/universal/.cursor/skills/citable/VERSION +1 -1
- package/dist/universal/.cursor/skills/citable/agents/citable-remediator.md +43 -0
- package/dist/universal/.cursor/skills/citable/agents/citable-sow-architect.md +43 -0
- package/dist/universal/.cursor/skills/citable/commands/README.md +13 -0
- package/dist/universal/.cursor/skills/citable/commands/remediation.md +55 -0
- package/dist/universal/.cursor/skills/citable/commands/reporting.md +28 -0
- package/dist/universal/.cursor/skills/citable/commands/sow.md +95 -0
- package/dist/universal/.cursor/skills/citable/manifest.json +12 -7
- package/dist/universal/.cursor/skills/citable/schemas/sow.schema.json +409 -0
- package/dist/universal/.gemini/skills/citable/SKILL.md +41 -1
- package/dist/universal/.gemini/skills/citable/VERSION +1 -1
- package/dist/universal/.gemini/skills/citable/agents/citable-remediator.md +43 -0
- package/dist/universal/.gemini/skills/citable/agents/citable-sow-architect.md +43 -0
- package/dist/universal/.gemini/skills/citable/commands/README.md +13 -0
- package/dist/universal/.gemini/skills/citable/commands/remediation.md +55 -0
- package/dist/universal/.gemini/skills/citable/commands/reporting.md +28 -0
- package/dist/universal/.gemini/skills/citable/commands/sow.md +95 -0
- package/dist/universal/.gemini/skills/citable/manifest.json +12 -7
- package/dist/universal/.gemini/skills/citable/schemas/sow.schema.json +409 -0
- package/dist/universal/.github/skills/citable/SKILL.md +41 -1
- package/dist/universal/.github/skills/citable/VERSION +1 -1
- package/dist/universal/.github/skills/citable/agents/citable-remediator.md +43 -0
- package/dist/universal/.github/skills/citable/agents/citable-sow-architect.md +43 -0
- package/dist/universal/.github/skills/citable/commands/README.md +13 -0
- package/dist/universal/.github/skills/citable/commands/remediation.md +55 -0
- package/dist/universal/.github/skills/citable/commands/reporting.md +28 -0
- package/dist/universal/.github/skills/citable/commands/sow.md +95 -0
- package/dist/universal/.github/skills/citable/manifest.json +12 -7
- package/dist/universal/.github/skills/citable/schemas/sow.schema.json +409 -0
- package/dist/universal/.kiro/skills/citable/SKILL.md +41 -1
- package/dist/universal/.kiro/skills/citable/VERSION +1 -1
- package/dist/universal/.kiro/skills/citable/agents/citable-remediator.md +43 -0
- package/dist/universal/.kiro/skills/citable/agents/citable-sow-architect.md +43 -0
- package/dist/universal/.kiro/skills/citable/commands/README.md +13 -0
- package/dist/universal/.kiro/skills/citable/commands/remediation.md +55 -0
- package/dist/universal/.kiro/skills/citable/commands/reporting.md +28 -0
- package/dist/universal/.kiro/skills/citable/commands/sow.md +95 -0
- package/dist/universal/.kiro/skills/citable/manifest.json +12 -7
- package/dist/universal/.kiro/skills/citable/schemas/sow.schema.json +409 -0
- package/dist/universal/.opencode/skills/citable/SKILL.md +41 -1
- package/dist/universal/.opencode/skills/citable/VERSION +1 -1
- package/dist/universal/.opencode/skills/citable/agents/citable-remediator.md +43 -0
- package/dist/universal/.opencode/skills/citable/agents/citable-sow-architect.md +43 -0
- package/dist/universal/.opencode/skills/citable/commands/README.md +13 -0
- package/dist/universal/.opencode/skills/citable/commands/remediation.md +55 -0
- package/dist/universal/.opencode/skills/citable/commands/reporting.md +28 -0
- package/dist/universal/.opencode/skills/citable/commands/sow.md +95 -0
- package/dist/universal/.opencode/skills/citable/manifest.json +12 -7
- package/dist/universal/.opencode/skills/citable/schemas/sow.schema.json +409 -0
- package/dist/universal/.pi/agent/skills/citable/SKILL.md +41 -1
- package/dist/universal/.pi/agent/skills/citable/VERSION +1 -1
- package/dist/universal/.pi/agent/skills/citable/agents/citable-remediator.md +43 -0
- package/dist/universal/.pi/agent/skills/citable/agents/citable-sow-architect.md +43 -0
- package/dist/universal/.pi/agent/skills/citable/commands/README.md +13 -0
- package/dist/universal/.pi/agent/skills/citable/commands/remediation.md +55 -0
- package/dist/universal/.pi/agent/skills/citable/commands/reporting.md +28 -0
- package/dist/universal/.pi/agent/skills/citable/commands/sow.md +95 -0
- package/dist/universal/.pi/agent/skills/citable/manifest.json +12 -7
- package/dist/universal/.pi/agent/skills/citable/schemas/sow.schema.json +409 -0
- package/dist/universal/.qoder/skills/citable/SKILL.md +41 -1
- package/dist/universal/.qoder/skills/citable/VERSION +1 -1
- package/dist/universal/.qoder/skills/citable/agents/citable-remediator.md +43 -0
- package/dist/universal/.qoder/skills/citable/agents/citable-sow-architect.md +43 -0
- package/dist/universal/.qoder/skills/citable/commands/README.md +13 -0
- package/dist/universal/.qoder/skills/citable/commands/remediation.md +55 -0
- package/dist/universal/.qoder/skills/citable/commands/reporting.md +28 -0
- package/dist/universal/.qoder/skills/citable/commands/sow.md +95 -0
- package/dist/universal/.qoder/skills/citable/manifest.json +12 -7
- package/dist/universal/.qoder/skills/citable/schemas/sow.schema.json +409 -0
- package/dist/universal/.rovodev/skills/citable/SKILL.md +41 -1
- package/dist/universal/.rovodev/skills/citable/VERSION +1 -1
- package/dist/universal/.rovodev/skills/citable/agents/citable-remediator.md +43 -0
- package/dist/universal/.rovodev/skills/citable/agents/citable-sow-architect.md +43 -0
- package/dist/universal/.rovodev/skills/citable/commands/README.md +13 -0
- package/dist/universal/.rovodev/skills/citable/commands/remediation.md +55 -0
- package/dist/universal/.rovodev/skills/citable/commands/reporting.md +28 -0
- package/dist/universal/.rovodev/skills/citable/commands/sow.md +95 -0
- package/dist/universal/.rovodev/skills/citable/manifest.json +12 -7
- package/dist/universal/.rovodev/skills/citable/schemas/sow.schema.json +409 -0
- package/dist/universal/.trae/skills/citable/SKILL.md +41 -1
- package/dist/universal/.trae/skills/citable/VERSION +1 -1
- package/dist/universal/.trae/skills/citable/agents/citable-remediator.md +43 -0
- package/dist/universal/.trae/skills/citable/agents/citable-sow-architect.md +43 -0
- package/dist/universal/.trae/skills/citable/commands/README.md +13 -0
- package/dist/universal/.trae/skills/citable/commands/remediation.md +55 -0
- package/dist/universal/.trae/skills/citable/commands/reporting.md +28 -0
- package/dist/universal/.trae/skills/citable/commands/sow.md +95 -0
- package/dist/universal/.trae/skills/citable/manifest.json +12 -7
- package/dist/universal/.trae/skills/citable/schemas/sow.schema.json +409 -0
- package/dist/universal/.trae-cn/skills/citable/SKILL.md +41 -1
- package/dist/universal/.trae-cn/skills/citable/VERSION +1 -1
- package/dist/universal/.trae-cn/skills/citable/agents/citable-remediator.md +43 -0
- package/dist/universal/.trae-cn/skills/citable/agents/citable-sow-architect.md +43 -0
- package/dist/universal/.trae-cn/skills/citable/commands/README.md +13 -0
- package/dist/universal/.trae-cn/skills/citable/commands/remediation.md +55 -0
- package/dist/universal/.trae-cn/skills/citable/commands/reporting.md +28 -0
- package/dist/universal/.trae-cn/skills/citable/commands/sow.md +95 -0
- package/dist/universal/.trae-cn/skills/citable/manifest.json +12 -7
- package/dist/universal/.trae-cn/skills/citable/schemas/sow.schema.json +409 -0
- package/dist/universal/manifest.json +150 -88
- package/package.json +1 -1
- package/schemas/sow.schema.json +409 -0
- package/skill/SKILL.md +41 -1
- package/skill/agents/citable-remediator.md +43 -0
- package/skill/agents/citable-sow-architect.md +43 -0
- package/skill/commands/README.md +13 -0
- package/skill/commands/remediation.md +55 -0
- package/skill/commands/reporting.md +28 -0
- package/skill/commands/sow.md +95 -0
- package/src/analysis/behavioral.js +126 -0
- package/src/analysis/croAudit.js +302 -0
- package/src/analysis/croRoadmap.js +121 -0
- package/src/analysis/eeat.js +300 -0
- package/src/analysis/funnelAnalysis.js +171 -0
- package/src/analysis/iceMatrix.js +205 -0
- package/src/analysis/offpage.js +228 -0
- package/src/analysis/readiness.js +253 -0
- package/src/analysis/schemaRecommendations.js +173 -0
- package/src/analysis/strategicRoadmap.js +148 -0
- package/src/cli/index.js +149 -3
- package/src/commands/answerEngineReadiness.js +48 -0
- package/src/commands/auditBacklinks.js +79 -0
- package/src/commands/croBacklog.js +182 -0
- package/src/commands/croSuite.js +156 -0
- package/src/commands/executive.js +6 -0
- package/src/commands/executiveCro.js +30 -0
- package/src/commands/executiveSearch.js +28 -0
- package/src/commands/inspectCro.js +7 -0
- package/src/commands/inspectEeat.js +53 -0
- package/src/commands/prioritize.js +42 -6
- package/src/commands/roadmapCmd.js +69 -0
- package/src/commands/schemaCmd.js +10 -2
- package/src/commands/sowCmd.js +84 -0
- package/src/commands/sweep.js +357 -0
- package/src/reporting/executiveCroReport.js +731 -0
- package/src/reporting/executiveExport.js +62 -0
- package/src/reporting/executiveSearchReport.js +704 -0
- package/src/sow/admissibilityGate.js +364 -0
- package/src/sow/generateSow.js +1178 -0
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enterprise Scope Admissibility Gate
|
|
3
|
+
*
|
|
4
|
+
* Filters raw SEO, AEO, GEO, SERP, and CRO findings into contractually admissible
|
|
5
|
+
* work packages, explicitly refusing findings that are exploratory, unverified,
|
|
6
|
+
* economically unjustified, out of organizational boundary, or lack testable acceptance.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const DEFAULT_ROLE_MAPPINGS = {
|
|
10
|
+
TECH: 'Supplier Engineering Lead',
|
|
11
|
+
TECHNICAL: 'Supplier Engineering Lead',
|
|
12
|
+
CRO: 'Supplier CRO Architect',
|
|
13
|
+
AEO: 'Supplier Discovery Specialist',
|
|
14
|
+
ANS: 'Supplier Discovery Specialist',
|
|
15
|
+
GEO: 'Supplier Discovery Specialist',
|
|
16
|
+
SCHEMA: 'Supplier Data Engineer',
|
|
17
|
+
ENTITY: 'Supplier Data Engineer',
|
|
18
|
+
SEO: 'Supplier Search Strategist',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Check whether a target subject falls within declared in-scope properties.
|
|
23
|
+
*
|
|
24
|
+
* Supported scope modes:
|
|
25
|
+
* - 'HOST_AND_SUBDOMAINS' (default): matches host and any subdomains
|
|
26
|
+
* - 'HOST': matches exact host only
|
|
27
|
+
* - 'EXACT_ORIGIN': matches exact scheme + host + port
|
|
28
|
+
* - 'PATH_PREFIX': matches host/subdomain and path prefix
|
|
29
|
+
* - 'EXACT_RESOURCE': matches exact host and path
|
|
30
|
+
*/
|
|
31
|
+
export function checkScopeBoundary(subject, inScopeProperties = [], scopeMode = 'HOST_AND_SUBDOMAINS') {
|
|
32
|
+
if (!inScopeProperties || inScopeProperties.length === 0) return true;
|
|
33
|
+
if (!subject) return false;
|
|
34
|
+
|
|
35
|
+
const subjectStr = typeof subject === 'string' ? subject : (subject.url || subject.identifier || '');
|
|
36
|
+
if (!subjectStr) return false;
|
|
37
|
+
|
|
38
|
+
const isRelative = !/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\/|^\/\//.test(subjectStr);
|
|
39
|
+
|
|
40
|
+
for (const prop of inScopeProperties) {
|
|
41
|
+
let propUrl;
|
|
42
|
+
try {
|
|
43
|
+
propUrl = new URL(prop.includes('://') ? prop : `https://${prop}`);
|
|
44
|
+
} catch {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const propHost = propUrl.hostname.toLowerCase();
|
|
48
|
+
const propPath = propUrl.pathname.replace(/\/+$/, '');
|
|
49
|
+
|
|
50
|
+
if (isRelative) {
|
|
51
|
+
if (scopeMode === 'PATH_PREFIX') {
|
|
52
|
+
const normSubj = subjectStr.startsWith('/') ? subjectStr : `/${subjectStr}`;
|
|
53
|
+
if (normSubj.startsWith(propPath || '/')) return true;
|
|
54
|
+
} else if (scopeMode === 'EXACT_RESOURCE') {
|
|
55
|
+
const normSubj = subjectStr.startsWith('/') ? subjectStr : `/${subjectStr}`;
|
|
56
|
+
if (normSubj === (propPath || '/')) return true;
|
|
57
|
+
} else {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let subjUrl;
|
|
64
|
+
try {
|
|
65
|
+
subjUrl = new URL(subjectStr);
|
|
66
|
+
} catch {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const subjHost = subjUrl.hostname.toLowerCase();
|
|
71
|
+
const subjPath = subjUrl.pathname.replace(/\/+$/, '');
|
|
72
|
+
|
|
73
|
+
switch (scopeMode) {
|
|
74
|
+
case 'EXACT_ORIGIN':
|
|
75
|
+
if (subjUrl.origin.toLowerCase() === propUrl.origin.toLowerCase()) return true;
|
|
76
|
+
break;
|
|
77
|
+
case 'HOST':
|
|
78
|
+
if (subjHost === propHost) return true;
|
|
79
|
+
break;
|
|
80
|
+
case 'HOST_AND_SUBDOMAINS':
|
|
81
|
+
if (subjHost === propHost || subjHost.endsWith(`.${propHost}`)) return true;
|
|
82
|
+
break;
|
|
83
|
+
case 'PATH_PREFIX':
|
|
84
|
+
if ((subjHost === propHost || subjHost.endsWith(`.${propHost}`)) &&
|
|
85
|
+
(subjPath === propPath || subjPath.startsWith(`${propPath}/`))) return true;
|
|
86
|
+
break;
|
|
87
|
+
case 'EXACT_RESOURCE':
|
|
88
|
+
if (subjHost === propHost && subjPath === propPath) return true;
|
|
89
|
+
break;
|
|
90
|
+
default:
|
|
91
|
+
if (subjHost === propHost || subjHost.endsWith(`.${propHost}`)) return true;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function evaluateScopeAdmissibility(findings = [], {
|
|
99
|
+
inScopeProperties = [],
|
|
100
|
+
scopeMode = 'HOST_AND_SUBDOMAINS',
|
|
101
|
+
minIceScore = 8.0,
|
|
102
|
+
allowedDisciplines = ['seo', 'aeo', 'geo', 'technical', 'schema', 'entity', 'cro'],
|
|
103
|
+
allowExperimental = false,
|
|
104
|
+
excludedDetectors = [],
|
|
105
|
+
roleMapping = null,
|
|
106
|
+
ownerMappingVersion = '1.0',
|
|
107
|
+
} = {}) {
|
|
108
|
+
const admitted = [];
|
|
109
|
+
const refused = [];
|
|
110
|
+
|
|
111
|
+
let reqIndex = 1;
|
|
112
|
+
let testIndex = 1;
|
|
113
|
+
const seenFindingIds = new Map();
|
|
114
|
+
|
|
115
|
+
for (let i = 0; i < findings.length; i++) {
|
|
116
|
+
const f = findings[i];
|
|
117
|
+
const subject = f.subject?.identifier || f.subject?.url || (typeof f.subject === 'string' ? f.subject : '/');
|
|
118
|
+
const detectorId = f.detector_id || (f.finding_id ? f.finding_id.replace(/^F-/, '') : `FINDING-${i + 1}`);
|
|
119
|
+
|
|
120
|
+
const rawFindingId = f.finding_id || f.id || f.detector_id || `FINDING-${i + 1}`;
|
|
121
|
+
let findingId = rawFindingId;
|
|
122
|
+
if (seenFindingIds.has(rawFindingId)) {
|
|
123
|
+
const count = seenFindingIds.get(rawFindingId) + 1;
|
|
124
|
+
seenFindingIds.set(rawFindingId, count);
|
|
125
|
+
findingId = `${rawFindingId}:${subject || count}`;
|
|
126
|
+
} else {
|
|
127
|
+
seenFindingIds.set(rawFindingId, 1);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const severity = f.classification?.severity || 'medium';
|
|
131
|
+
const confidence = f.classification?.confidence || 'observed';
|
|
132
|
+
const evidenceList = f.observation?.evidence || [];
|
|
133
|
+
const remediation = f.remediation?.preferred || (typeof f.remediation === 'string' ? f.remediation : '');
|
|
134
|
+
|
|
135
|
+
// Gate 1: Excluded detectors / explicitly blacklisted checks
|
|
136
|
+
if (excludedDetectors.includes(detectorId) || excludedDetectors.includes(findingId)) {
|
|
137
|
+
refused.push({
|
|
138
|
+
finding_id: findingId,
|
|
139
|
+
detector_id: detectorId,
|
|
140
|
+
subject,
|
|
141
|
+
gate_failed: 'excluded_detector',
|
|
142
|
+
refusal_code: 'REFUSE-EXCLUDED',
|
|
143
|
+
refusal_rationale: `Detector ${detectorId} is explicitly excluded from contracted SOW delivery scope.`,
|
|
144
|
+
suggested_handling: 'Track in internal backlog; execute under separate advisory engagement.',
|
|
145
|
+
});
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Gate 2: Evidence Maturity Gate
|
|
150
|
+
if (!allowExperimental && (confidence === 'experimental' || f.classification?.finding_type === 'experimental')) {
|
|
151
|
+
refused.push({
|
|
152
|
+
finding_id: findingId,
|
|
153
|
+
detector_id: detectorId,
|
|
154
|
+
subject,
|
|
155
|
+
gate_failed: 'evidence_maturity',
|
|
156
|
+
refusal_code: 'REFUSE-EXPERIMENTAL',
|
|
157
|
+
refusal_rationale: 'Finding is experimental and lacks deterministic, reproducible evidence. Enterprise SOW requires established empirical certainty.',
|
|
158
|
+
suggested_handling: 'Run controlled observation probes before considering for contractual obligation.',
|
|
159
|
+
});
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (!Array.isArray(evidenceList) || evidenceList.length === 0) {
|
|
164
|
+
refused.push({
|
|
165
|
+
finding_id: findingId,
|
|
166
|
+
detector_id: detectorId,
|
|
167
|
+
subject,
|
|
168
|
+
gate_failed: 'evidence_maturity',
|
|
169
|
+
refusal_code: 'REFUSE-NO-EVIDENCE',
|
|
170
|
+
refusal_rationale: 'Finding contains no documented supporting evidence in audit package. Cannot contract work without verifiable evidence.',
|
|
171
|
+
suggested_handling: 'Re-audit target with full evidence collection enabled.',
|
|
172
|
+
});
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Gate 4: Scope Boundary Gate
|
|
177
|
+
if (inScopeProperties.length > 0) {
|
|
178
|
+
const inScope = checkScopeBoundary(f.subject || subject, inScopeProperties, scopeMode);
|
|
179
|
+
if (!inScope) {
|
|
180
|
+
refused.push({
|
|
181
|
+
finding_id: findingId,
|
|
182
|
+
detector_id: detectorId,
|
|
183
|
+
subject,
|
|
184
|
+
gate_failed: 'scope_boundary',
|
|
185
|
+
refusal_code: 'REFUSE-OUT-OF-SCOPE',
|
|
186
|
+
refusal_rationale: `Subject "${subject}" is outside declared contractual in-scope properties (${inScopeProperties.join(', ')}).`,
|
|
187
|
+
suggested_handling: 'Submit Scope Change Request (SCR) to expand in-scope property boundary.',
|
|
188
|
+
});
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Gate 5: Technical Feasibility & Remediation Safety
|
|
194
|
+
const remLower = remediation.toLowerCase();
|
|
195
|
+
if (!remediation || remLower.includes('consult external legal') || remLower.includes('out of scope') || remLower.includes('unfeasible')) {
|
|
196
|
+
refused.push({
|
|
197
|
+
finding_id: findingId,
|
|
198
|
+
detector_id: detectorId,
|
|
199
|
+
subject,
|
|
200
|
+
gate_failed: 'technical_feasibility',
|
|
201
|
+
refusal_code: 'REFUSE-UNFEASIBLE-REMEDIATION',
|
|
202
|
+
refusal_rationale: 'Finding requires external legal advice, lacks defined engineering remediation method, or is declared technically unfeasible.',
|
|
203
|
+
suggested_handling: 'Refer to customer legal counsel or appropriate third party; exclude from engineering delivery.',
|
|
204
|
+
});
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Gate 6: Commercial Materiality & ICE-BV Gate
|
|
209
|
+
const isCoreSeverity = ['critical', 'high'].includes(severity.toLowerCase());
|
|
210
|
+
const isVitalsOrFunnel = ['TECH-001', 'TECH-002', 'CRO-001', 'CRO-002', 'CRO-005', 'CRO-006', 'CRO-007', 'CRO-013', 'CRO-021', 'SCHEMA-001'].includes(detectorId);
|
|
211
|
+
const iceScore = typeof f.ice_score === 'number' ? f.ice_score : (isCoreSeverity ? 12 : 6);
|
|
212
|
+
|
|
213
|
+
if (!isCoreSeverity && !isVitalsOrFunnel && iceScore < minIceScore) {
|
|
214
|
+
refused.push({
|
|
215
|
+
finding_id: findingId,
|
|
216
|
+
detector_id: detectorId,
|
|
217
|
+
subject,
|
|
218
|
+
gate_failed: 'commercial_materiality',
|
|
219
|
+
refusal_code: 'REFUSE-LOW-MATERIALITY',
|
|
220
|
+
refusal_rationale: `Finding has low severity (${severity}) and ICE score (${iceScore} < ${minIceScore}), failing economic justification threshold for core SOW fee envelope.`,
|
|
221
|
+
suggested_handling: 'Defer to customer internal operations backlog or future optimization phase.',
|
|
222
|
+
});
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Gate 7: Objectively Testable Acceptance Gate
|
|
227
|
+
const hasVerification = Boolean(
|
|
228
|
+
f.verification?.detector_to_rerun ||
|
|
229
|
+
f.verification?.method ||
|
|
230
|
+
f.verification?.acceptance_test
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
if (!hasVerification) {
|
|
234
|
+
refused.push({
|
|
235
|
+
finding_id: findingId,
|
|
236
|
+
detector_id: detectorId,
|
|
237
|
+
subject,
|
|
238
|
+
gate_failed: 'measurable_acceptance',
|
|
239
|
+
refusal_code: 'REFUSE-UNVERIFIABLE',
|
|
240
|
+
refusal_rationale: 'Finding lacks an automated verification rerun detector or objective pass/fail validation method.',
|
|
241
|
+
suggested_handling: 'Define deterministic verification criteria before adding to contractual scope.',
|
|
242
|
+
});
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Gate 8: Ownership Clarity Gate
|
|
247
|
+
const effectiveRoles = { ...DEFAULT_ROLE_MAPPINGS, ...(roleMapping || {}) };
|
|
248
|
+
const disciplineKey = (f.discipline?.[0] || detectorId.split('-')[0] || 'TECH').toUpperCase();
|
|
249
|
+
|
|
250
|
+
let resolvedOwner = null;
|
|
251
|
+
let ownerSource = null;
|
|
252
|
+
|
|
253
|
+
if (f.delivery_owner && f.delivery_owner !== 'UNRESOLVED') {
|
|
254
|
+
resolvedOwner = f.delivery_owner;
|
|
255
|
+
ownerSource = 'finding.delivery_owner';
|
|
256
|
+
} else if (f.remediation?.owner && f.remediation.owner !== 'UNRESOLVED') {
|
|
257
|
+
resolvedOwner = f.remediation.owner;
|
|
258
|
+
ownerSource = 'finding.remediation.owner';
|
|
259
|
+
} else if (f.owner && f.owner !== 'UNRESOLVED') {
|
|
260
|
+
resolvedOwner = f.owner;
|
|
261
|
+
ownerSource = 'finding.owner';
|
|
262
|
+
} else if (effectiveRoles[disciplineKey] && effectiveRoles[disciplineKey] !== 'UNRESOLVED') {
|
|
263
|
+
resolvedOwner = effectiveRoles[disciplineKey];
|
|
264
|
+
ownerSource = 'engagement_role_mapping';
|
|
265
|
+
} else if (effectiveRoles[detectorId] && effectiveRoles[detectorId] !== 'UNRESOLVED') {
|
|
266
|
+
resolvedOwner = effectiveRoles[detectorId];
|
|
267
|
+
ownerSource = 'engagement_role_mapping';
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (!resolvedOwner || resolvedOwner === 'UNRESOLVED' || resolvedOwner.toLowerCase() === 'unassigned') {
|
|
271
|
+
refused.push({
|
|
272
|
+
finding_id: findingId,
|
|
273
|
+
detector_id: detectorId,
|
|
274
|
+
subject,
|
|
275
|
+
gate_failed: 'ownership_clarity',
|
|
276
|
+
refusal_code: 'REFUSE-OWNER-UNRESOLVED',
|
|
277
|
+
refusal_rationale: `Responsible delivery owner could not be resolved for finding ${findingId} (discipline: ${disciplineKey}).`,
|
|
278
|
+
suggested_handling: 'Specify delivery_owner on finding or map discipline to accountable delivery role in SOW configuration.',
|
|
279
|
+
});
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Gate 8b: Discipline Authorization Gate
|
|
284
|
+
const rawDisciplines = Array.isArray(f.discipline)
|
|
285
|
+
? f.discipline
|
|
286
|
+
: (f.discipline ? [f.discipline] : [detectorId.split('-')[0]]);
|
|
287
|
+
const findingDisciplines = rawDisciplines.map((d) => String(d).toLowerCase());
|
|
288
|
+
|
|
289
|
+
if (allowedDisciplines && allowedDisciplines.length > 0) {
|
|
290
|
+
const normAllowed = allowedDisciplines.map((d) => String(d).toLowerCase());
|
|
291
|
+
const isAuthorized = findingDisciplines.some((d) => normAllowed.includes(d));
|
|
292
|
+
if (!isAuthorized) {
|
|
293
|
+
refused.push({
|
|
294
|
+
finding_id: findingId,
|
|
295
|
+
detector_id: detectorId,
|
|
296
|
+
subject,
|
|
297
|
+
gate_failed: 'discipline_authorization',
|
|
298
|
+
refusal_code: 'REFUSE-DISCIPLINE-NOT-AUTHORIZED',
|
|
299
|
+
refusal_rationale: `Discipline (${findingDisciplines.join(', ')}) is not authorized under contracted delivery disciplines (${allowedDisciplines.join(', ')}).`,
|
|
300
|
+
suggested_handling: 'Expand authorized disciplines in SOW engagement terms or contract under separate statement of work.',
|
|
301
|
+
});
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Passed all gates -> Admitted to Contractual SOW Scope
|
|
307
|
+
let wpId = 'WP-TECH-01';
|
|
308
|
+
let wpName = 'Technical Search & Performance Infrastructure';
|
|
309
|
+
let deliverableId = 'DELIV-01';
|
|
310
|
+
|
|
311
|
+
if (disciplineKey === 'CRO' || detectorId.startsWith('CRO-')) {
|
|
312
|
+
wpId = 'WP-CRO-02';
|
|
313
|
+
wpName = 'Conversion Funnel & Offer Architecture Optimization';
|
|
314
|
+
deliverableId = 'DELIV-02';
|
|
315
|
+
} else if (disciplineKey === 'AEO' || disciplineKey === 'GEO' || disciplineKey === 'ANS') {
|
|
316
|
+
wpId = 'WP-AEO-03';
|
|
317
|
+
wpName = 'Answer-Engine (AEO) & Generative Search Extraction';
|
|
318
|
+
deliverableId = 'DELIV-03';
|
|
319
|
+
} else if (disciplineKey === 'SCHEMA' || disciplineKey === 'ENTITY') {
|
|
320
|
+
wpId = 'WP-SCHEMA-04';
|
|
321
|
+
wpName = 'Structured Data & Entity Knowledge Graph Architecture';
|
|
322
|
+
deliverableId = 'DELIV-04';
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const sowReqId = `REQ-SOW-${String(reqIndex++).padStart(3, '0')}`;
|
|
326
|
+
const accTestId = `ACC-TEST-${String(testIndex++).padStart(3, '0')}`;
|
|
327
|
+
const evidenceId = evidenceList[0];
|
|
328
|
+
const detectorToRerun = f.verification?.detector_to_rerun || detectorId;
|
|
329
|
+
|
|
330
|
+
admitted.push({
|
|
331
|
+
finding_id: findingId,
|
|
332
|
+
detector_id: detectorId,
|
|
333
|
+
sow_requirement_id: sowReqId,
|
|
334
|
+
work_package_id: wpId,
|
|
335
|
+
work_package_name: wpName,
|
|
336
|
+
deliverable_id: deliverableId,
|
|
337
|
+
acceptance_test_id: accTestId,
|
|
338
|
+
subject,
|
|
339
|
+
severity,
|
|
340
|
+
confidence,
|
|
341
|
+
recommendation: remediation,
|
|
342
|
+
acceptance_test: f.verification?.acceptance_test || `Execute closed-loop verification: citable verify remediation --finding ${detectorToRerun} --target ${subject} (Expect status: RESOLVED, zero regressions)`,
|
|
343
|
+
owner: resolvedOwner,
|
|
344
|
+
owner_source: ownerSource,
|
|
345
|
+
owner_mapping_version: ownerMappingVersion,
|
|
346
|
+
evidence_id: evidenceId,
|
|
347
|
+
evidence_ids: [...evidenceList],
|
|
348
|
+
commercial_priority: isCoreSeverity ? 'P1_CRITICAL' : 'P2_HIGH_VALUE',
|
|
349
|
+
ice_bv_score: iceScore,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return {
|
|
354
|
+
fact_status: 'admissibility_gate_evaluated',
|
|
355
|
+
summary: {
|
|
356
|
+
total_evaluated: findings.length,
|
|
357
|
+
admitted_count: admitted.length,
|
|
358
|
+
refused_count: refused.length,
|
|
359
|
+
admissibility_rate_pct: findings.length > 0 ? Math.round((admitted.length / findings.length) * 100) : 0,
|
|
360
|
+
},
|
|
361
|
+
admitted_requirements: admitted,
|
|
362
|
+
refusal_log: refused,
|
|
363
|
+
};
|
|
364
|
+
}
|