@nebulacomponents/citable 1.18.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 +40 -0
- package/dist/universal/.agents/skills/citable/SKILL.md +22 -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 -8
- package/dist/universal/.agents/skills/citable/schemas/sow.schema.json +342 -25
- 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 +22 -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 -10
- package/dist/universal/.claude/skills/citable/schemas/sow.schema.json +342 -25
- package/dist/universal/.cursor/skills/citable/SKILL.md +22 -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 -8
- package/dist/universal/.cursor/skills/citable/schemas/sow.schema.json +342 -25
- package/dist/universal/.gemini/skills/citable/SKILL.md +22 -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 -8
- package/dist/universal/.gemini/skills/citable/schemas/sow.schema.json +342 -25
- package/dist/universal/.github/skills/citable/SKILL.md +22 -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 -8
- package/dist/universal/.github/skills/citable/schemas/sow.schema.json +342 -25
- package/dist/universal/.kiro/skills/citable/SKILL.md +22 -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 -8
- package/dist/universal/.kiro/skills/citable/schemas/sow.schema.json +342 -25
- package/dist/universal/.opencode/skills/citable/SKILL.md +22 -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 -8
- package/dist/universal/.opencode/skills/citable/schemas/sow.schema.json +342 -25
- package/dist/universal/.pi/agent/skills/citable/SKILL.md +22 -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 -8
- package/dist/universal/.pi/agent/skills/citable/schemas/sow.schema.json +342 -25
- package/dist/universal/.qoder/skills/citable/SKILL.md +22 -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 -8
- package/dist/universal/.qoder/skills/citable/schemas/sow.schema.json +342 -25
- package/dist/universal/.rovodev/skills/citable/SKILL.md +22 -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 -8
- package/dist/universal/.rovodev/skills/citable/schemas/sow.schema.json +342 -25
- package/dist/universal/.trae/skills/citable/SKILL.md +22 -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 -8
- package/dist/universal/.trae/skills/citable/schemas/sow.schema.json +342 -25
- package/dist/universal/.trae-cn/skills/citable/SKILL.md +22 -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 -8
- package/dist/universal/.trae-cn/skills/citable/schemas/sow.schema.json +342 -25
- package/dist/universal/manifest.json +150 -100
- package/package.json +1 -1
- package/schemas/sow.schema.json +342 -25
- package/skill/SKILL.md +22 -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/commands/sowCmd.js +17 -0
- package/src/sow/admissibilityGate.js +216 -46
- package/src/sow/generateSow.js +529 -113
|
@@ -71,3 +71,31 @@ any observed change to a specific intervention.
|
|
|
71
71
|
authoritative index guarantees. Divergences are reported as factual observations,
|
|
72
72
|
never fabricated or interpolated.
|
|
73
73
|
|
|
74
|
+
# Enterprise Search Intelligence Briefing (`citable report search`)
|
|
75
|
+
|
|
76
|
+
1. Run `citable report search [--target <dir|url>] [--run <id>] [--format md|html|json] [--output <file>]`
|
|
77
|
+
to generate the 19-pillar Executive Search Intelligence Briefing.
|
|
78
|
+
2. Synthesizes technical crawlability, indexation, Core Web Vitals readiness,
|
|
79
|
+
AEO answer-extractability, GEO citation frequency, knowledge graph schema
|
|
80
|
+
architecture, and competitor SERP landscape into an executive document.
|
|
81
|
+
3. Every recommendation binds to a verified finding in the Evidence Register.
|
|
82
|
+
4. Facts and inferences are strictly segregated: deterministic observations are
|
|
83
|
+
reported in green data panels; strategic hypotheses and inferences are explicitly
|
|
84
|
+
labeled with confidence ratings.
|
|
85
|
+
5. Refusal boundary: Never promises rankings, algorithmic immunity, or indexing
|
|
86
|
+
guarantees.
|
|
87
|
+
|
|
88
|
+
# Enterprise CRO & Customer Journey Briefing (`citable report cro`)
|
|
89
|
+
|
|
90
|
+
1. Run `citable report cro [--target <dir|url>] [--input <file>] [--format md|html|json] [--output <file>]`
|
|
91
|
+
to generate the 25-pillar Executive Conversion & Customer Journey Briefing.
|
|
92
|
+
2. Covers end-to-end conversion paths: above-the-fold clarity, message-match
|
|
93
|
+
scoring, cognitive load, checkout friction, trust and objection analysis, offer
|
|
94
|
+
architecture, and prioritized A/B experiment blueprints.
|
|
95
|
+
3. Strictly adheres to the **Observation vs Hypothesis vs Causation** taxonomy:
|
|
96
|
+
- **Observations**: Verbatim DOM defects (e.g. missing autocomplete, undersized touch targets).
|
|
97
|
+
- **Hypotheses**: Falsifiable experiment statements with measurable primary metrics and guardrails.
|
|
98
|
+
- **Causation**: Statistical lift attribution requiring concluded, SRM-clean A/B experiments.
|
|
99
|
+
4. Refusal boundary: Revenue lift, conversion percentage improvements, and visual
|
|
100
|
+
saliency scores are modeled heuristic indices, never observed behavior or guaranteed revenue.
|
|
101
|
+
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
---
|
|
2
|
+
command: /citable sow generate [--target <dir|url>] [--run <id>] [--budget <amount>] [--budget-minor <cents>] [--format md|html|json] [--output <path>] [--sample] [--draft]
|
|
3
|
+
purpose: Generate a contractually binding, evidence-traceable enterprise Statement of Work (SOW) from verified audit findings through a formal 7-gate scope admissibility filter and 7-column traceability matrix.
|
|
4
|
+
preconditions: [at least one finalized audit run with verified findings or live target; sample mode required for synthetic demonstrations]
|
|
5
|
+
failure_behaviour: absent or unverified findings in contractual mode -> NoFindingsError; all findings refused -> NoAdmissibleRequirementsError; non-existent run -> RunNotFoundError; negative or sub-cent budget -> BudgetCalculationError; invariant drift -> SowInvariantError
|
|
6
|
+
artifacts_created: [SOW.md, SOW.html, or SOW.json at specified output path]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Enterprise Statement of Work (SOW) Contract
|
|
10
|
+
|
|
11
|
+
The `citable sow` command generates enterprise-grade, evidence-backed Statements of Work derived from verified technical SEO, AEO, GEO, schema, and CRO audit findings. It establishes rigid, downward traceability:
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
Evidence (observation envelope)
|
|
15
|
+
↓
|
|
16
|
+
Observation (deterministic DOM / HTTP probe)
|
|
17
|
+
↓
|
|
18
|
+
Finding (verified defect / friction)
|
|
19
|
+
↓
|
|
20
|
+
Recommendation (governed remediation)
|
|
21
|
+
↓
|
|
22
|
+
SOW Requirement (contractual scope)
|
|
23
|
+
↓
|
|
24
|
+
Deliverable (code patch / AST modification)
|
|
25
|
+
↓
|
|
26
|
+
Acceptance Test (automated closed-loop detector rerun)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## 1. Operating Premises & Commercial Disclaimers
|
|
30
|
+
|
|
31
|
+
1. **Fee Binding**: Supplier fees are bound exclusively to verified deliverable acceptance and closed-loop test execution.
|
|
32
|
+
2. **Strict Outcome Disclaimers**: In strict compliance with Citable governance principles, **no search engine crawling, indexation, ranking, AI answer citation, or conversion revenue is guaranteed**.
|
|
33
|
+
3. **No Synthetic Data in Contracts**: Attempting to generate a `CONTRACTUAL` SOW without verified empirical findings immediately fails closed with `NoFindingsError`. Synthetic findings are strictly confined to `--sample` / `--demo`.
|
|
34
|
+
|
|
35
|
+
## 2. The Scope Admissibility Gate
|
|
36
|
+
|
|
37
|
+
Audit findings do NOT automatically become contractual obligations. Every candidate finding must pass 7 admissibility gates:
|
|
38
|
+
|
|
39
|
+
| Gate | Verification Check | Failure Refusal Code | Contractual Handling |
|
|
40
|
+
| :--- | :--- | :--- | :--- |
|
|
41
|
+
| **1. Exclusion Gate** | Check against explicit excluded detectors list | `REFUSE-EXCLUDED` | Track in internal backlog; execute under separate advisory engagement. |
|
|
42
|
+
| **2. Discipline Authorization** | Check if discipline is authorized under contract | `REFUSE-DISCIPLINE-NOT-AUTHORIZED` | Expand authorized disciplines in SOW engagement terms or contract under separate SOW. |
|
|
43
|
+
| **3. Evidence Maturity** | Require deterministic/observed confidence & evidence | `REFUSE-EXPERIMENTAL` / `REFUSE-NO-EVIDENCE` | Run controlled observation probes before considering for contractual obligation. |
|
|
44
|
+
| **4. Scope Boundary** | Verify URL matches authorized host, subdomain, or path | `REFUSE-OUT-OF-SCOPE` | Escalate domain ownership verification or expand contractual executive scope. |
|
|
45
|
+
| **5. Feasibility Gate** | Reject legal, trademark, GDPR, or unfeasible remediation | `REFUSE-UNFEASIBLE-REMEDIATION` | Refer to specialized legal or compliance counsel outside scope. |
|
|
46
|
+
| **6. Materiality Gate** | Require critical/high severity or ICE score >= threshold | `REFUSE-LOW-MATERIALITY` | Log to internal product maintenance backlog; omit from high-value SOW. |
|
|
47
|
+
| **7. Acceptance Gate** | Require automated rerun detector or objective pass/fail test | `REFUSE-UNVERIFIABLE` | Define deterministic verification criteria before adding to scope. |
|
|
48
|
+
| **8. Ownership Clarity** | Resolve accountable owner from finding or role mappings | `REFUSE-OWNER-UNRESOLVED` | Specify delivery_owner on finding or map discipline to accountable role. |
|
|
49
|
+
|
|
50
|
+
All refused findings are recorded in the SOW's `admissibility_gate.refusal_log` with standard refusal codes and contractual handling guidance.
|
|
51
|
+
|
|
52
|
+
## 3. The 7-Column Traceability Matrix
|
|
53
|
+
|
|
54
|
+
Every admitted requirement must satisfy complete 7-column bidirectional traceability:
|
|
55
|
+
|
|
56
|
+
1. **Finding ID**: Verbatim identifier of the empirical defect (e.g. `F-TECH-001`).
|
|
57
|
+
2. **Recommendation**: Governed, safe remediation instruction.
|
|
58
|
+
3. **SOW Requirement ID**: Contractual clause identifier (`REQ-SOW-001`).
|
|
59
|
+
4. **Deliverable ID**: Accountable work package deliverable (`DELIV-01`).
|
|
60
|
+
5. **Acceptance Test ID**: Automated closed-loop test (`ACC-TEST-001`).
|
|
61
|
+
6. **Responsible Owner**: Named lead dev, architect, or agency practice owner.
|
|
62
|
+
7. **Source Evidence IDs**: Array of cryptographic observation IDs (`['EVD-CWV-001']`).
|
|
63
|
+
|
|
64
|
+
## 4. Commercial Currency & Minor-Unit Semantics
|
|
65
|
+
|
|
66
|
+
To eliminate commercial fee drift and rounding leaks, all internal calculations use exact **integer minor units (cents, exponent 2, currency `USD`)**:
|
|
67
|
+
|
|
68
|
+
* `currency`: Declared as `USD`.
|
|
69
|
+
* `currency_minor_unit_exponent`: Declared as `2`.
|
|
70
|
+
* `commercial_total_fee_minor`: Integer cents (e.g. `$50,000.00` = `5000000` cents).
|
|
71
|
+
* `milestones[].fee_minor`: Integer floor allocation across milestones with remainder pennies distributed deterministically to initial milestones.
|
|
72
|
+
* `commercial_total_fee_usd` / `milestones[].fee_usd`: Derived dollar representation formatted at presentation boundaries (`$33.34`, `$50,000`).
|
|
73
|
+
* Sub-cent fractional budgets (`100.001`) and negative budgets are strictly rejected.
|
|
74
|
+
|
|
75
|
+
## 5. Generation Modes & Machine-Readable Provenance
|
|
76
|
+
|
|
77
|
+
Every generated SOW embeds an immutable `generation_provenance` block:
|
|
78
|
+
|
|
79
|
+
* **`CONTRACTUAL`**: Generated from verified historical run findings or live target; synthetic evidence forbidden.
|
|
80
|
+
* **`DRAFT`**: Generated with preliminary scope or unfinalized commercial terms; requires executive sign-off.
|
|
81
|
+
* **`NON_CONTRACTUAL_SAMPLE`**: Generated with synthetic baseline findings strictly for evaluation/demo (`--sample`).
|
|
82
|
+
|
|
83
|
+
Provenance tracks:
|
|
84
|
+
* `generator_version`: Exact Citable semver.
|
|
85
|
+
* `generated_at`: ISO 8601 timestamp.
|
|
86
|
+
* `source_type`: `HISTORICAL_RUN`, `LIVE_INSPECTION`, `DIRECT_FINDINGS`, or `SAMPLE_BASELINE`.
|
|
87
|
+
* `source_identifier`: Target URL, run ID, or sample identifier.
|
|
88
|
+
* `source_findings_count`: Integer count of evaluated findings.
|
|
89
|
+
* `findings_integrity_hash`: SHA-256 hash of input findings.
|
|
90
|
+
|
|
91
|
+
## 6. SOW Verification Command
|
|
92
|
+
|
|
93
|
+
Run `citable sow validate <sow.json>` to verify:
|
|
94
|
+
1. Strict schema compliance against `schemas/sow.schema.json` (`additionalProperties: false`).
|
|
95
|
+
2. Cross-object invariants via `validateSowInvariants` (fee balancing, work package and deliverable parity, non-empty evidence links).
|
package/src/commands/sowCmd.js
CHANGED
|
@@ -32,14 +32,22 @@ export async function sowCommand(argv = [], root = process.cwd()) {
|
|
|
32
32
|
target: args.target,
|
|
33
33
|
baseUrl: args.baseUrl,
|
|
34
34
|
runId: args.runId,
|
|
35
|
+
live: args.live,
|
|
36
|
+
sample: args.sample,
|
|
37
|
+
demo: args.demo,
|
|
38
|
+
draft: args.draft,
|
|
35
39
|
client: args.client || 'Enterprise Customer',
|
|
36
40
|
clientContact: args.clientContact || 'procurement@customer.test',
|
|
37
41
|
supplier: args.supplier || 'Nebula Components & Citable Advisory Practice',
|
|
38
42
|
supplierContact: args.supplierContact || 'advisory@nebulacomponents.test',
|
|
39
43
|
budget: args.budget ? Number(args.budget) : 45000,
|
|
44
|
+
budgetMinor: args.budgetMinor ? Number(args.budgetMinor) : null,
|
|
40
45
|
termDays: args.term ? Number(args.term) : 90,
|
|
41
46
|
format: args.format || 'markdown',
|
|
42
47
|
output: args.output,
|
|
48
|
+
inScopeProperties: args.inScope ? args.inScope.split(',').map((s) => s.trim()) : [],
|
|
49
|
+
scopeMode: args.scopeMode || 'HOST_AND_SUBDOMAINS',
|
|
50
|
+
minIceScore: args.minIce ? Number(args.minIce) : 8.0,
|
|
43
51
|
});
|
|
44
52
|
|
|
45
53
|
return result;
|
|
@@ -52,16 +60,25 @@ function parseSowArgs(argv) {
|
|
|
52
60
|
if (a === '--target') args.target = argv[++i];
|
|
53
61
|
else if (a === '--base-url') args.baseUrl = argv[++i];
|
|
54
62
|
else if (a === '--run') args.runId = argv[++i];
|
|
63
|
+
else if (a === '--live') args.live = true;
|
|
64
|
+
else if (a === '--sample') args.sample = true;
|
|
65
|
+
else if (a === '--demo') args.demo = true;
|
|
66
|
+
else if (a === '--draft') args.draft = true;
|
|
55
67
|
else if (a === '--client') args.client = argv[++i];
|
|
56
68
|
else if (a === '--client-contact') args.clientContact = argv[++i];
|
|
57
69
|
else if (a === '--supplier') args.supplier = argv[++i];
|
|
58
70
|
else if (a === '--supplier-contact') args.supplierContact = argv[++i];
|
|
59
71
|
else if (a === '--budget') args.budget = argv[++i];
|
|
72
|
+
else if (a === '--budget-minor') args.budgetMinor = argv[++i];
|
|
60
73
|
else if (a === '--term') args.term = argv[++i];
|
|
61
74
|
else if (a === '--format') args.format = argv[++i];
|
|
62
75
|
else if (a === '--output') args.output = argv[++i];
|
|
63
76
|
else if (a === '--json') args.format = 'json';
|
|
77
|
+
else if (a === '--scope-mode' || a === '--scope') args.scopeMode = argv[++i];
|
|
78
|
+
else if (a === '--in-scope') args.inScope = argv[++i];
|
|
79
|
+
else if (a === '--min-ice') args.minIce = argv[++i];
|
|
64
80
|
else args._.push(a);
|
|
65
81
|
}
|
|
66
82
|
return args;
|
|
67
83
|
}
|
|
84
|
+
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { safePath } from '../detectors/framework.js';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* Enterprise Scope Admissibility Gate
|
|
5
3
|
*
|
|
@@ -7,32 +5,138 @@ import { safePath } from '../detectors/framework.js';
|
|
|
7
5
|
* work packages, explicitly refusing findings that are exploratory, unverified,
|
|
8
6
|
* economically unjustified, out of organizational boundary, or lack testable acceptance.
|
|
9
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
|
+
|
|
10
98
|
export function evaluateScopeAdmissibility(findings = [], {
|
|
11
99
|
inScopeProperties = [],
|
|
100
|
+
scopeMode = 'HOST_AND_SUBDOMAINS',
|
|
12
101
|
minIceScore = 8.0,
|
|
13
102
|
allowedDisciplines = ['seo', 'aeo', 'geo', 'technical', 'schema', 'entity', 'cro'],
|
|
14
103
|
allowExperimental = false,
|
|
15
104
|
excludedDetectors = [],
|
|
105
|
+
roleMapping = null,
|
|
106
|
+
ownerMappingVersion = '1.0',
|
|
16
107
|
} = {}) {
|
|
17
108
|
const admitted = [];
|
|
18
109
|
const refused = [];
|
|
19
110
|
|
|
20
111
|
let reqIndex = 1;
|
|
21
112
|
let testIndex = 1;
|
|
113
|
+
const seenFindingIds = new Map();
|
|
22
114
|
|
|
23
115
|
for (let i = 0; i < findings.length; i++) {
|
|
24
116
|
const f = findings[i];
|
|
25
|
-
const
|
|
26
|
-
const
|
|
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
|
+
|
|
27
130
|
const severity = f.classification?.severity || 'medium';
|
|
28
131
|
const confidence = f.classification?.confidence || 'observed';
|
|
29
132
|
const evidenceList = f.observation?.evidence || [];
|
|
30
|
-
const remediation = f.remediation?.preferred || '';
|
|
133
|
+
const remediation = f.remediation?.preferred || (typeof f.remediation === 'string' ? f.remediation : '');
|
|
31
134
|
|
|
32
135
|
// Gate 1: Excluded detectors / explicitly blacklisted checks
|
|
33
|
-
if (excludedDetectors.includes(detectorId)) {
|
|
136
|
+
if (excludedDetectors.includes(detectorId) || excludedDetectors.includes(findingId)) {
|
|
34
137
|
refused.push({
|
|
35
|
-
finding_id:
|
|
138
|
+
finding_id: findingId,
|
|
139
|
+
detector_id: detectorId,
|
|
36
140
|
subject,
|
|
37
141
|
gate_failed: 'excluded_detector',
|
|
38
142
|
refusal_code: 'REFUSE-EXCLUDED',
|
|
@@ -45,7 +149,8 @@ export function evaluateScopeAdmissibility(findings = [], {
|
|
|
45
149
|
// Gate 2: Evidence Maturity Gate
|
|
46
150
|
if (!allowExperimental && (confidence === 'experimental' || f.classification?.finding_type === 'experimental')) {
|
|
47
151
|
refused.push({
|
|
48
|
-
finding_id:
|
|
152
|
+
finding_id: findingId,
|
|
153
|
+
detector_id: detectorId,
|
|
49
154
|
subject,
|
|
50
155
|
gate_failed: 'evidence_maturity',
|
|
51
156
|
refusal_code: 'REFUSE-EXPERIMENTAL',
|
|
@@ -55,9 +160,10 @@ export function evaluateScopeAdmissibility(findings = [], {
|
|
|
55
160
|
continue;
|
|
56
161
|
}
|
|
57
162
|
|
|
58
|
-
if (evidenceList.length === 0) {
|
|
163
|
+
if (!Array.isArray(evidenceList) || evidenceList.length === 0) {
|
|
59
164
|
refused.push({
|
|
60
|
-
finding_id:
|
|
165
|
+
finding_id: findingId,
|
|
166
|
+
detector_id: detectorId,
|
|
61
167
|
subject,
|
|
62
168
|
gate_failed: 'evidence_maturity',
|
|
63
169
|
refusal_code: 'REFUSE-NO-EVIDENCE',
|
|
@@ -67,17 +173,13 @@ export function evaluateScopeAdmissibility(findings = [], {
|
|
|
67
173
|
continue;
|
|
68
174
|
}
|
|
69
175
|
|
|
70
|
-
// Gate
|
|
176
|
+
// Gate 4: Scope Boundary Gate
|
|
71
177
|
if (inScopeProperties.length > 0) {
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
const sStem = subject.replace(/^https?:\/\//, '').replace(/\/+$/, '').toLowerCase();
|
|
75
|
-
return sStem.includes(pStem) || pStem.includes(sStem);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
if (!isInScope) {
|
|
178
|
+
const inScope = checkScopeBoundary(f.subject || subject, inScopeProperties, scopeMode);
|
|
179
|
+
if (!inScope) {
|
|
79
180
|
refused.push({
|
|
80
|
-
finding_id:
|
|
181
|
+
finding_id: findingId,
|
|
182
|
+
detector_id: detectorId,
|
|
81
183
|
subject,
|
|
82
184
|
gate_failed: 'scope_boundary',
|
|
83
185
|
refusal_code: 'REFUSE-OUT-OF-SCOPE',
|
|
@@ -88,28 +190,30 @@ export function evaluateScopeAdmissibility(findings = [], {
|
|
|
88
190
|
}
|
|
89
191
|
}
|
|
90
192
|
|
|
91
|
-
// Gate
|
|
92
|
-
|
|
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')) {
|
|
93
196
|
refused.push({
|
|
94
|
-
finding_id:
|
|
197
|
+
finding_id: findingId,
|
|
198
|
+
detector_id: detectorId,
|
|
95
199
|
subject,
|
|
96
200
|
gate_failed: 'technical_feasibility',
|
|
97
201
|
refusal_code: 'REFUSE-UNFEASIBLE-REMEDIATION',
|
|
98
|
-
refusal_rationale: 'Finding requires external legal advice
|
|
99
|
-
suggested_handling: 'Refer to customer legal counsel; exclude from engineering delivery.',
|
|
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.',
|
|
100
204
|
});
|
|
101
205
|
continue;
|
|
102
206
|
}
|
|
103
207
|
|
|
104
|
-
// Gate
|
|
105
|
-
|
|
106
|
-
const isCoreSeverity = ['critical', 'high'].includes(severity);
|
|
208
|
+
// Gate 6: Commercial Materiality & ICE-BV Gate
|
|
209
|
+
const isCoreSeverity = ['critical', 'high'].includes(severity.toLowerCase());
|
|
107
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);
|
|
108
|
-
const iceScore = f.ice_score
|
|
211
|
+
const iceScore = typeof f.ice_score === 'number' ? f.ice_score : (isCoreSeverity ? 12 : 6);
|
|
109
212
|
|
|
110
213
|
if (!isCoreSeverity && !isVitalsOrFunnel && iceScore < minIceScore) {
|
|
111
214
|
refused.push({
|
|
112
|
-
finding_id:
|
|
215
|
+
finding_id: findingId,
|
|
216
|
+
detector_id: detectorId,
|
|
113
217
|
subject,
|
|
114
218
|
gate_failed: 'commercial_materiality',
|
|
115
219
|
refusal_code: 'REFUSE-LOW-MATERIALITY',
|
|
@@ -119,11 +223,17 @@ export function evaluateScopeAdmissibility(findings = [], {
|
|
|
119
223
|
continue;
|
|
120
224
|
}
|
|
121
225
|
|
|
122
|
-
// Gate
|
|
123
|
-
const
|
|
124
|
-
|
|
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) {
|
|
125
234
|
refused.push({
|
|
126
|
-
finding_id:
|
|
235
|
+
finding_id: findingId,
|
|
236
|
+
detector_id: detectorId,
|
|
127
237
|
subject,
|
|
128
238
|
gate_failed: 'measurable_acceptance',
|
|
129
239
|
refusal_code: 'REFUSE-UNVERIFIABLE',
|
|
@@ -133,36 +243,93 @@ export function evaluateScopeAdmissibility(findings = [], {
|
|
|
133
243
|
continue;
|
|
134
244
|
}
|
|
135
245
|
|
|
136
|
-
//
|
|
137
|
-
const
|
|
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
|
|
138
307
|
let wpId = 'WP-TECH-01';
|
|
139
308
|
let wpName = 'Technical Search & Performance Infrastructure';
|
|
140
309
|
let deliverableId = 'DELIV-01';
|
|
141
|
-
let owner = 'Supplier Engineering Lead';
|
|
142
310
|
|
|
143
|
-
if (
|
|
311
|
+
if (disciplineKey === 'CRO' || detectorId.startsWith('CRO-')) {
|
|
144
312
|
wpId = 'WP-CRO-02';
|
|
145
313
|
wpName = 'Conversion Funnel & Offer Architecture Optimization';
|
|
146
314
|
deliverableId = 'DELIV-02';
|
|
147
|
-
|
|
148
|
-
} else if (discipline === 'AEO' || discipline === 'GEO' || discipline === 'ANS') {
|
|
315
|
+
} else if (disciplineKey === 'AEO' || disciplineKey === 'GEO' || disciplineKey === 'ANS') {
|
|
149
316
|
wpId = 'WP-AEO-03';
|
|
150
317
|
wpName = 'Answer-Engine (AEO) & Generative Search Extraction';
|
|
151
318
|
deliverableId = 'DELIV-03';
|
|
152
|
-
|
|
153
|
-
} else if (discipline === 'SCHEMA' || discipline === 'ENTITY') {
|
|
319
|
+
} else if (disciplineKey === 'SCHEMA' || disciplineKey === 'ENTITY') {
|
|
154
320
|
wpId = 'WP-SCHEMA-04';
|
|
155
321
|
wpName = 'Structured Data & Entity Knowledge Graph Architecture';
|
|
156
322
|
deliverableId = 'DELIV-04';
|
|
157
|
-
owner = 'Supplier Data Engineer';
|
|
158
323
|
}
|
|
159
324
|
|
|
160
325
|
const sowReqId = `REQ-SOW-${String(reqIndex++).padStart(3, '0')}`;
|
|
161
326
|
const accTestId = `ACC-TEST-${String(testIndex++).padStart(3, '0')}`;
|
|
162
|
-
const evidenceId = evidenceList[0]
|
|
327
|
+
const evidenceId = evidenceList[0];
|
|
328
|
+
const detectorToRerun = f.verification?.detector_to_rerun || detectorId;
|
|
163
329
|
|
|
164
330
|
admitted.push({
|
|
165
|
-
finding_id:
|
|
331
|
+
finding_id: findingId,
|
|
332
|
+
detector_id: detectorId,
|
|
166
333
|
sow_requirement_id: sowReqId,
|
|
167
334
|
work_package_id: wpId,
|
|
168
335
|
work_package_name: wpName,
|
|
@@ -172,9 +339,12 @@ export function evaluateScopeAdmissibility(findings = [], {
|
|
|
172
339
|
severity,
|
|
173
340
|
confidence,
|
|
174
341
|
recommendation: remediation,
|
|
175
|
-
acceptance_test: `Execute closed-loop verification: citable verify remediation --finding ${
|
|
176
|
-
owner,
|
|
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,
|
|
177
346
|
evidence_id: evidenceId,
|
|
347
|
+
evidence_ids: [...evidenceList],
|
|
178
348
|
commercial_priority: isCoreSeverity ? 'P1_CRITICAL' : 'P2_HIGH_VALUE',
|
|
179
349
|
ice_bv_score: iceScore,
|
|
180
350
|
});
|