@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.
Files changed (137) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/dist/universal/.agents/skills/citable/SKILL.md +22 -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 -8
  11. package/dist/universal/.agents/skills/citable/schemas/sow.schema.json +342 -25
  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 +22 -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 -10
  24. package/dist/universal/.claude/skills/citable/schemas/sow.schema.json +342 -25
  25. package/dist/universal/.cursor/skills/citable/SKILL.md +22 -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 -8
  34. package/dist/universal/.cursor/skills/citable/schemas/sow.schema.json +342 -25
  35. package/dist/universal/.gemini/skills/citable/SKILL.md +22 -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 -8
  44. package/dist/universal/.gemini/skills/citable/schemas/sow.schema.json +342 -25
  45. package/dist/universal/.github/skills/citable/SKILL.md +22 -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 -8
  54. package/dist/universal/.github/skills/citable/schemas/sow.schema.json +342 -25
  55. package/dist/universal/.kiro/skills/citable/SKILL.md +22 -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 -8
  64. package/dist/universal/.kiro/skills/citable/schemas/sow.schema.json +342 -25
  65. package/dist/universal/.opencode/skills/citable/SKILL.md +22 -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 -8
  74. package/dist/universal/.opencode/skills/citable/schemas/sow.schema.json +342 -25
  75. package/dist/universal/.pi/agent/skills/citable/SKILL.md +22 -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 -8
  84. package/dist/universal/.pi/agent/skills/citable/schemas/sow.schema.json +342 -25
  85. package/dist/universal/.qoder/skills/citable/SKILL.md +22 -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 -8
  94. package/dist/universal/.qoder/skills/citable/schemas/sow.schema.json +342 -25
  95. package/dist/universal/.rovodev/skills/citable/SKILL.md +22 -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 -8
  104. package/dist/universal/.rovodev/skills/citable/schemas/sow.schema.json +342 -25
  105. package/dist/universal/.trae/skills/citable/SKILL.md +22 -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 -8
  114. package/dist/universal/.trae/skills/citable/schemas/sow.schema.json +342 -25
  115. package/dist/universal/.trae-cn/skills/citable/SKILL.md +22 -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 -8
  124. package/dist/universal/.trae-cn/skills/citable/schemas/sow.schema.json +342 -25
  125. package/dist/universal/manifest.json +150 -100
  126. package/package.json +1 -1
  127. package/schemas/sow.schema.json +342 -25
  128. package/skill/SKILL.md +22 -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/commands/sowCmd.js +17 -0
  136. package/src/sow/admissibilityGate.js +216 -46
  137. 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).
@@ -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 detectorId = f.detector_id || `FINDING-${i + 1}`;
26
- const subject = f.subject?.identifier || f.subject?.url || '/';
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: detectorId,
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: detectorId,
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: detectorId,
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 3: Scope Boundary Gate
176
+ // Gate 4: Scope Boundary Gate
71
177
  if (inScopeProperties.length > 0) {
72
- const isInScope = inScopeProperties.some((prop) => {
73
- const pStem = prop.replace(/^https?:\/\//, '').replace(/\/+$/, '').toLowerCase();
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: detectorId,
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 4: Technical Feasibility & Remediation Safety
92
- if (!remediation || remediation.toLowerCase().includes('consult external legal') || remediation.toLowerCase().includes('out of scope')) {
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: detectorId,
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 or lacks defined engineering remediation method.',
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 5: Commercial Materiality & ICE-BV Gate
105
- // High & critical severity are automatically material; medium/low must meet ICE threshold or be key conversion/vitals defects
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 || (isCoreSeverity ? 12 : 6);
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: detectorId,
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 6: Objectively Testable Acceptance Gate
123
- const rerunDetector = f.verification?.detector_to_rerun || detectorId;
124
- if (!rerunDetector && !f.verification?.method) {
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: detectorId,
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
- // Passed All 6 Gates -> Admitted to Contractual SOW Scope
137
- const discipline = (f.discipline?.[0] || detectorId.split('-')[0] || 'TECH').toUpperCase();
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 (discipline === 'CRO' || detectorId.startsWith('CRO-')) {
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
- owner = 'Supplier CRO Architect';
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
- owner = 'Supplier Discovery Specialist';
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] || `EVD-OBS-${detectorId}`;
327
+ const evidenceId = evidenceList[0];
328
+ const detectorToRerun = f.verification?.detector_to_rerun || detectorId;
163
329
 
164
330
  admitted.push({
165
- finding_id: detectorId,
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 ${detectorId} --target ${subject} (Expect status: RESOLVED, zero regressions)`,
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
  });