@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.
Files changed (164) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/dist/universal/.agents/skills/citable/SKILL.md +41 -1
  3. package/dist/universal/.agents/skills/citable/VERSION +1 -1
  4. package/dist/universal/.agents/skills/citable/agents/citable-remediator.md +43 -0
  5. package/dist/universal/.agents/skills/citable/agents/citable-sow-architect.md +43 -0
  6. package/dist/universal/.agents/skills/citable/commands/README.md +13 -0
  7. package/dist/universal/.agents/skills/citable/commands/remediation.md +55 -0
  8. package/dist/universal/.agents/skills/citable/commands/reporting.md +28 -0
  9. package/dist/universal/.agents/skills/citable/commands/sow.md +95 -0
  10. package/dist/universal/.agents/skills/citable/manifest.json +12 -7
  11. package/dist/universal/.agents/skills/citable/schemas/sow.schema.json +409 -0
  12. package/dist/universal/.claude/agents/citable/citable-remediator.md +43 -0
  13. package/dist/universal/.claude/agents/citable/citable-sow-architect.md +43 -0
  14. package/dist/universal/.claude/agents/citable/manifest.json +6 -4
  15. package/dist/universal/.claude/skills/citable/SKILL.md +41 -1
  16. package/dist/universal/.claude/skills/citable/VERSION +1 -1
  17. package/dist/universal/.claude/skills/citable/agents/citable-remediator.md +43 -0
  18. package/dist/universal/.claude/skills/citable/agents/citable-sow-architect.md +43 -0
  19. package/dist/universal/.claude/skills/citable/commands/README.md +13 -0
  20. package/dist/universal/.claude/skills/citable/commands/remediation.md +55 -0
  21. package/dist/universal/.claude/skills/citable/commands/reporting.md +28 -0
  22. package/dist/universal/.claude/skills/citable/commands/sow.md +95 -0
  23. package/dist/universal/.claude/skills/citable/manifest.json +16 -9
  24. package/dist/universal/.claude/skills/citable/schemas/sow.schema.json +409 -0
  25. package/dist/universal/.cursor/skills/citable/SKILL.md +41 -1
  26. package/dist/universal/.cursor/skills/citable/VERSION +1 -1
  27. package/dist/universal/.cursor/skills/citable/agents/citable-remediator.md +43 -0
  28. package/dist/universal/.cursor/skills/citable/agents/citable-sow-architect.md +43 -0
  29. package/dist/universal/.cursor/skills/citable/commands/README.md +13 -0
  30. package/dist/universal/.cursor/skills/citable/commands/remediation.md +55 -0
  31. package/dist/universal/.cursor/skills/citable/commands/reporting.md +28 -0
  32. package/dist/universal/.cursor/skills/citable/commands/sow.md +95 -0
  33. package/dist/universal/.cursor/skills/citable/manifest.json +12 -7
  34. package/dist/universal/.cursor/skills/citable/schemas/sow.schema.json +409 -0
  35. package/dist/universal/.gemini/skills/citable/SKILL.md +41 -1
  36. package/dist/universal/.gemini/skills/citable/VERSION +1 -1
  37. package/dist/universal/.gemini/skills/citable/agents/citable-remediator.md +43 -0
  38. package/dist/universal/.gemini/skills/citable/agents/citable-sow-architect.md +43 -0
  39. package/dist/universal/.gemini/skills/citable/commands/README.md +13 -0
  40. package/dist/universal/.gemini/skills/citable/commands/remediation.md +55 -0
  41. package/dist/universal/.gemini/skills/citable/commands/reporting.md +28 -0
  42. package/dist/universal/.gemini/skills/citable/commands/sow.md +95 -0
  43. package/dist/universal/.gemini/skills/citable/manifest.json +12 -7
  44. package/dist/universal/.gemini/skills/citable/schemas/sow.schema.json +409 -0
  45. package/dist/universal/.github/skills/citable/SKILL.md +41 -1
  46. package/dist/universal/.github/skills/citable/VERSION +1 -1
  47. package/dist/universal/.github/skills/citable/agents/citable-remediator.md +43 -0
  48. package/dist/universal/.github/skills/citable/agents/citable-sow-architect.md +43 -0
  49. package/dist/universal/.github/skills/citable/commands/README.md +13 -0
  50. package/dist/universal/.github/skills/citable/commands/remediation.md +55 -0
  51. package/dist/universal/.github/skills/citable/commands/reporting.md +28 -0
  52. package/dist/universal/.github/skills/citable/commands/sow.md +95 -0
  53. package/dist/universal/.github/skills/citable/manifest.json +12 -7
  54. package/dist/universal/.github/skills/citable/schemas/sow.schema.json +409 -0
  55. package/dist/universal/.kiro/skills/citable/SKILL.md +41 -1
  56. package/dist/universal/.kiro/skills/citable/VERSION +1 -1
  57. package/dist/universal/.kiro/skills/citable/agents/citable-remediator.md +43 -0
  58. package/dist/universal/.kiro/skills/citable/agents/citable-sow-architect.md +43 -0
  59. package/dist/universal/.kiro/skills/citable/commands/README.md +13 -0
  60. package/dist/universal/.kiro/skills/citable/commands/remediation.md +55 -0
  61. package/dist/universal/.kiro/skills/citable/commands/reporting.md +28 -0
  62. package/dist/universal/.kiro/skills/citable/commands/sow.md +95 -0
  63. package/dist/universal/.kiro/skills/citable/manifest.json +12 -7
  64. package/dist/universal/.kiro/skills/citable/schemas/sow.schema.json +409 -0
  65. package/dist/universal/.opencode/skills/citable/SKILL.md +41 -1
  66. package/dist/universal/.opencode/skills/citable/VERSION +1 -1
  67. package/dist/universal/.opencode/skills/citable/agents/citable-remediator.md +43 -0
  68. package/dist/universal/.opencode/skills/citable/agents/citable-sow-architect.md +43 -0
  69. package/dist/universal/.opencode/skills/citable/commands/README.md +13 -0
  70. package/dist/universal/.opencode/skills/citable/commands/remediation.md +55 -0
  71. package/dist/universal/.opencode/skills/citable/commands/reporting.md +28 -0
  72. package/dist/universal/.opencode/skills/citable/commands/sow.md +95 -0
  73. package/dist/universal/.opencode/skills/citable/manifest.json +12 -7
  74. package/dist/universal/.opencode/skills/citable/schemas/sow.schema.json +409 -0
  75. package/dist/universal/.pi/agent/skills/citable/SKILL.md +41 -1
  76. package/dist/universal/.pi/agent/skills/citable/VERSION +1 -1
  77. package/dist/universal/.pi/agent/skills/citable/agents/citable-remediator.md +43 -0
  78. package/dist/universal/.pi/agent/skills/citable/agents/citable-sow-architect.md +43 -0
  79. package/dist/universal/.pi/agent/skills/citable/commands/README.md +13 -0
  80. package/dist/universal/.pi/agent/skills/citable/commands/remediation.md +55 -0
  81. package/dist/universal/.pi/agent/skills/citable/commands/reporting.md +28 -0
  82. package/dist/universal/.pi/agent/skills/citable/commands/sow.md +95 -0
  83. package/dist/universal/.pi/agent/skills/citable/manifest.json +12 -7
  84. package/dist/universal/.pi/agent/skills/citable/schemas/sow.schema.json +409 -0
  85. package/dist/universal/.qoder/skills/citable/SKILL.md +41 -1
  86. package/dist/universal/.qoder/skills/citable/VERSION +1 -1
  87. package/dist/universal/.qoder/skills/citable/agents/citable-remediator.md +43 -0
  88. package/dist/universal/.qoder/skills/citable/agents/citable-sow-architect.md +43 -0
  89. package/dist/universal/.qoder/skills/citable/commands/README.md +13 -0
  90. package/dist/universal/.qoder/skills/citable/commands/remediation.md +55 -0
  91. package/dist/universal/.qoder/skills/citable/commands/reporting.md +28 -0
  92. package/dist/universal/.qoder/skills/citable/commands/sow.md +95 -0
  93. package/dist/universal/.qoder/skills/citable/manifest.json +12 -7
  94. package/dist/universal/.qoder/skills/citable/schemas/sow.schema.json +409 -0
  95. package/dist/universal/.rovodev/skills/citable/SKILL.md +41 -1
  96. package/dist/universal/.rovodev/skills/citable/VERSION +1 -1
  97. package/dist/universal/.rovodev/skills/citable/agents/citable-remediator.md +43 -0
  98. package/dist/universal/.rovodev/skills/citable/agents/citable-sow-architect.md +43 -0
  99. package/dist/universal/.rovodev/skills/citable/commands/README.md +13 -0
  100. package/dist/universal/.rovodev/skills/citable/commands/remediation.md +55 -0
  101. package/dist/universal/.rovodev/skills/citable/commands/reporting.md +28 -0
  102. package/dist/universal/.rovodev/skills/citable/commands/sow.md +95 -0
  103. package/dist/universal/.rovodev/skills/citable/manifest.json +12 -7
  104. package/dist/universal/.rovodev/skills/citable/schemas/sow.schema.json +409 -0
  105. package/dist/universal/.trae/skills/citable/SKILL.md +41 -1
  106. package/dist/universal/.trae/skills/citable/VERSION +1 -1
  107. package/dist/universal/.trae/skills/citable/agents/citable-remediator.md +43 -0
  108. package/dist/universal/.trae/skills/citable/agents/citable-sow-architect.md +43 -0
  109. package/dist/universal/.trae/skills/citable/commands/README.md +13 -0
  110. package/dist/universal/.trae/skills/citable/commands/remediation.md +55 -0
  111. package/dist/universal/.trae/skills/citable/commands/reporting.md +28 -0
  112. package/dist/universal/.trae/skills/citable/commands/sow.md +95 -0
  113. package/dist/universal/.trae/skills/citable/manifest.json +12 -7
  114. package/dist/universal/.trae/skills/citable/schemas/sow.schema.json +409 -0
  115. package/dist/universal/.trae-cn/skills/citable/SKILL.md +41 -1
  116. package/dist/universal/.trae-cn/skills/citable/VERSION +1 -1
  117. package/dist/universal/.trae-cn/skills/citable/agents/citable-remediator.md +43 -0
  118. package/dist/universal/.trae-cn/skills/citable/agents/citable-sow-architect.md +43 -0
  119. package/dist/universal/.trae-cn/skills/citable/commands/README.md +13 -0
  120. package/dist/universal/.trae-cn/skills/citable/commands/remediation.md +55 -0
  121. package/dist/universal/.trae-cn/skills/citable/commands/reporting.md +28 -0
  122. package/dist/universal/.trae-cn/skills/citable/commands/sow.md +95 -0
  123. package/dist/universal/.trae-cn/skills/citable/manifest.json +12 -7
  124. package/dist/universal/.trae-cn/skills/citable/schemas/sow.schema.json +409 -0
  125. package/dist/universal/manifest.json +150 -88
  126. package/package.json +1 -1
  127. package/schemas/sow.schema.json +409 -0
  128. package/skill/SKILL.md +41 -1
  129. package/skill/agents/citable-remediator.md +43 -0
  130. package/skill/agents/citable-sow-architect.md +43 -0
  131. package/skill/commands/README.md +13 -0
  132. package/skill/commands/remediation.md +55 -0
  133. package/skill/commands/reporting.md +28 -0
  134. package/skill/commands/sow.md +95 -0
  135. package/src/analysis/behavioral.js +126 -0
  136. package/src/analysis/croAudit.js +302 -0
  137. package/src/analysis/croRoadmap.js +121 -0
  138. package/src/analysis/eeat.js +300 -0
  139. package/src/analysis/funnelAnalysis.js +171 -0
  140. package/src/analysis/iceMatrix.js +205 -0
  141. package/src/analysis/offpage.js +228 -0
  142. package/src/analysis/readiness.js +253 -0
  143. package/src/analysis/schemaRecommendations.js +173 -0
  144. package/src/analysis/strategicRoadmap.js +148 -0
  145. package/src/cli/index.js +149 -3
  146. package/src/commands/answerEngineReadiness.js +48 -0
  147. package/src/commands/auditBacklinks.js +79 -0
  148. package/src/commands/croBacklog.js +182 -0
  149. package/src/commands/croSuite.js +156 -0
  150. package/src/commands/executive.js +6 -0
  151. package/src/commands/executiveCro.js +30 -0
  152. package/src/commands/executiveSearch.js +28 -0
  153. package/src/commands/inspectCro.js +7 -0
  154. package/src/commands/inspectEeat.js +53 -0
  155. package/src/commands/prioritize.js +42 -6
  156. package/src/commands/roadmapCmd.js +69 -0
  157. package/src/commands/schemaCmd.js +10 -2
  158. package/src/commands/sowCmd.js +84 -0
  159. package/src/commands/sweep.js +357 -0
  160. package/src/reporting/executiveCroReport.js +731 -0
  161. package/src/reporting/executiveExport.js +62 -0
  162. package/src/reporting/executiveSearchReport.js +704 -0
  163. package/src/sow/admissibilityGate.js +364 -0
  164. 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
+ }