# Polyglot Codebase Knowledge Graph

> Generated offline by **readmenator**. 191 files, 3183 symbols, 1301 imports. Supports C, C++, Python, Go, Rust, JS/TS, Java, C#, Shell, PHP, Dart, GDScript, Nim, ASM, Ruby, Swift, Kotlin, Scala, Lua, Elixir.
> No LLMs. No tokens. Pure static analysis. See more [here](https://github.com/grisuno/ReadMenator)

**Start here:** Statistics Dashboard for scope, God Nodes for blast radius, Architecture Reference for per-file API. Agents: prefer `readmenator-agent/INDEX.md` + `SYMBOLS.md`.

**Wiki:** prefer `readmenator-wiki/index.md` for progressive disclosure: one synthesis page per community, `connections.json` with EXTRACTED vs INFERRED confidence, `queries.md` log, `REPORT.md` audit.

**Confidence:** EXTRACTED = parsed from source, INFERRED = heuristic bridge, AMBIGUOUS = reported, never hidden. See `readmenator-wiki/REPORT.md`.

**Total Files Parsed:** 191 | **Total Symbols Extracted:** 3183 | **Total Imports:** 1301
 | **Resolved Imports:** 412

<!-- ranking_model: v1.0 | weights: {ppr:0.45,auth:0.2,test:0.15,doc:0.1,fresh:0.1} | alpha:0.85 | commit:4bf09f0 | date:2026-07-18 -->


## Table of Contents

1. [Statistics Dashboard](#statistics-dashboard)
2. [Architectural Layers](#architectural-layers)
3. [Ranked Context](#ranked-context)
4. [God Nodes](#god-nodes)
5. [Community Analysis](#community-analysis)
6. [Surprising Connections](#surprising-connections)
7. [Suggested Questions](#suggested-questions)
8. [Force-Graph Explorer](#force-graph-explorer)
9. [Corpus Analytics](#corpus-analytics)
10. [Taint Propagation Map](#taint-propagation-map)
11. [Hotspot Analysis](#hotspot-analysis)
12. [Dependency Cycles](#dependency-cycles)
13. [Change Impact Analysis](#change-impact-analysis)
14. [Architecture Violations](#architecture-violations)
15. [Suggested Linting Rules](#suggested-linting-rules)
16. [Concept Graph](#concept-graph)
17. [Orphans](#orphans)
18. [Query Recipes](#query-recipes)
19. [Structural Knowledge Map](#structural-knowledge-map)
20. [UML Class Diagram](#uml-class-diagram)
21. [Code Property Graph](#code-property-graph)
22. [Architecture Reference](#architecture-reference)
    - [JS (4 files)](#js-4-files)
    - [PY (185 files)](#py-185-files)
    - [SH (2 files)](#sh-2-files)

---

## Statistics Dashboard

| Metric | Value |
|--------|-------|
| Total Files | 191 |
| Total Symbols | 3183 |
| Total Imports | 1301 |
| Call Edges | 16621 |
| Inheritance Edges | 53 |
| Languages | 3 |
| Avg Symbols/File | 16.7 |
| Avg Imports/File | 6.8 |
| Resolved Imports | 412 |

### Top Files by Import Count (Fan-Out)

| File | Imports | Symbols | Language |
|------|---------|---------|----------|
| `estorides_web.py` | 64 | 97 | py |
| `test_security_remediation.py` | 43 | 67 | py |
| `estorides_cli.py` | 42 | 31 | py |
| `orchestrator.py` | 32 | 18 | py |
| `test_system_app_sources.py` | 23 | 42 | py |
| `async_client.py` | 19 | 21 | py |
| `test_graph_bundle.py` | 19 | 30 | py |
| `feeds.py` | 16 | 16 | py |
| `monitoring.py` | 16 | 26 | py |
| `ontology.py` | 16 | 25 | py |

---

## Architectural Layers

Auto-detected from path patterns, naming conventions, and imported frameworks.

| Layer | Files |
|-------|-------|
| testing | 90 |
| utility | 87 |
| business_logic | 5 |
| data_access | 3 |
| infrastructure | 3 |
| presentation | 3 |

### utility

- `gb_shots.py` (py, 0 symbols)
- `gfv2_bridge.py` (py, 0 symbols)
- `gfv2_click.py` (py, 0 symbols)
- `gfv2_debug.py` (py, 0 symbols)
- `gfv2_dom.py` (py, 0 symbols)
- `gfv2_func.py` (py, 0 symbols)
- `gfv2_raw.py` (py, 0 symbols)
- `gfv2_shots.py` (py, 0 symbols)
- `gfv2_step.py` (py, 0 symbols)
- `gfv2_time.py` (py, 0 symbols)
- `app.py` (py, 0 symbols)
- `estorides_cli.py` (py, 31 symbols)
- `__init__.py` (py, 0 symbols)
- `active_recon.py` (py, 25 symbols)
- `alerter.py` (py, 15 symbols)
- *... and 72 more*

### data_access

- `gfv2_data.py` (py, 0 symbols)
- `fusion_store.py` (py, 18 symbols)
- `sqlite_store.py` (py, 8 symbols)

### testing

- `_multi_test.sh` (sh, 0 symbols)
- `conftest.py` (py, 0 symbols)
- `conftest.py` (py, 0 symbols)
- `test_change_detection_properties.py` (py, 8 symbols)
- `test_csp_safe_styles_properties.py` (py, 3 symbols)
- `test_graph_bundle_properties.py` (py, 6 symbols)
- `test_hypothesis_engine_properties.py` (py, 9 symbols)
- `test_observation_models_properties.py` (py, 7 symbols)
- `test_parsers_properties.py` (py, 1 symbols)
- `test_recon_fusion_properties.py` (py, 17 symbols)
- `test_reliability_scoring_properties.py` (py, 12 symbols)
- `test_search_telemetry_properties.py` (py, 6 symbols)
- `test_source_health_monitoring_properties.py` (py, 7 symbols)
- `test_system_app_sources_properties.py` (py, 6 symbols)
- `test_target_management_properties.py` (py, 10 symbols)
- *... and 75 more*

### infrastructure

- `async_client.py` (py, 21 symbols)
- `config.py` (py, 29 symbols)
- `event_bus.py` (py, 9 symbols)

### business_logic

- `entity_extraction.py` (py, 21 symbols)
- `entity_resolution.py` (py, 35 symbols)
- `entity_store.py` (py, 5 symbols)
- `lan_policy.py` (py, 9 symbols)
- `observation_models.py` (py, 14 symbols)

### presentation

- `web_security.py` (py, 22 symbols)
- `estorides_web.py` (py, 97 symbols)
- `estorides_web_tools.py` (py, 5 symbols)

---

## Ranked Context

Files ranked by composite score for the current query context. The ranking combines Personalized PageRank (query relevance), global authority, test coverage, documentation coverage, and code freshness. Model: v1.0.

| Rank | File | Composite | PPR | Authority | Test | Doc |
|------|------|-----------|-----|-----------|------|-----|
| 1 | `test_security_remediation.py` | 0.2070 | 0.4157 | 0.0030 | 0.00 | 0.19 |
| 2 | `ids.py` | 0.2030 | 0.0000 | 0.0148 | 0.00 | 2.00 |
| 3 | `intelligence_prompts.py` | 0.2011 | 0.0000 | 0.0055 | 0.00 | 2.00 |
| 4 | `openapi.py` | 0.2007 | 0.0000 | 0.0034 | 0.00 | 2.00 |
| 5 | `test_graph_bundle_assets.py` | 0.2000 | 0.0000 | 0.0000 | 0.00 | 2.00 |
| 6 | `config.py` | 0.1697 | 0.0924 | 0.1576 | 0.00 | 0.97 |
| 7 | `ssrf_guard.py` | 0.1650 | 0.1930 | 0.0161 | 0.00 | 0.75 |
| 8 | `alerter.py` | 0.1611 | 0.1631 | 0.0054 | 0.00 | 0.87 |
| 9 | `sync_docs.py` | 0.1506 | 0.0000 | 0.0030 | 0.00 | 1.50 |
| 10 | `test_csp_safe_styles_properties.py` | 0.1339 | 0.0000 | 0.0030 | 0.00 | 1.33 |

**Query anchors:** tests/test_security_remediation.py

**Top result justification paths:**

  `test_security_remediation.py`

---

## God Nodes

Most architecturally central files ranked by combined import/export degree and symbol richness.

| File | Score | Connections | PageRank |
|------|-------|-------------|----------|
| `config.py` | 88.9 | | 0.1576 |
| `estorides_web.py` | 83.7 | | 0.0000 |
| `orchestrator.py` | 57.8 | | 0.0000 |
| `estorides_cli.py` | 33.1 | | 0.0000 |
| `entity_extraction.py` | 30.1 | | 0.0000 |
| `__init__.py` | 28.0 | | 0.0000 |
| `tool_runner.py` | 25.5 | | 0.0000 |
| `estorides.js` | 24.0 | | 0.0000 |
| `cases.py` | 22.1 | | 0.0000 |
| `fusion_store.py` | 21.8 | | 0.0000 |

---

## Community Analysis

Files grouped by import-based community detection. Cohesion measures how tightly connected each community is internally.

### estorides_core: estorides_web (Cohesion: 0.51)

**27 files** in this community:

- `app.py` (py, 0 symbols)
- `audit.py` (py, 11 symbols)
- `graph_rag_search.py` (py, 44 symbols)
- `job_registry.py` (py, 10 symbols)
- `lan_web.py` (py, 6 symbols)
- `openapi.py` (py, 1 symbols)
- `ops_observability.py` (py, 10 symbols)
- `search_telemetry.py` (py, 22 symbols)
- `web_security.py` (py, 22 symbols)
- `estorides_web.py` (py, 97 symbols)
- `estorides_web_tools.py` (py, 5 symbols)
- `test_csp_safe_styles_properties.py` (py, 3 symbols)
- `test_search_telemetry_properties.py` (py, 6 symbols)
- `test_audit_log.py` (py, 5 symbols)
- `test_auth_gate.py` (py, 10 symbols)
- `test_csp_safe_styles.py` (py, 11 symbols)
- `test_graph_rag_search.py` (py, 11 symbols)
- `test_job_registry.py` (py, 8 symbols)
- `test_map_basemap.py` (py, 5 symbols)
- `test_openapi.py` (py, 1 symbols)
- ... and 7 more files

### estorides_core: parsers (Cohesion: 0.47)

**24 files** in this community:

- `discoverer.py` (py, 21 symbols)
- `event_bus.py` (py, 9 symbols)
- `graph_kuzu.py` (py, 11 symbols)
- `mitre_attack.py` (py, 4 symbols)
- `orchestrator.py` (py, 18 symbols)
- `pagination.py` (py, 7 symbols)
- `parsers.py` (py, 65 symbols)
- `relationship_inference.py` (py, 16 symbols)
- `socmint.py` (py, 12 symbols)
- `source_loader.py` (py, 20 symbols)
- `system_app_sources.py` (py, 32 symbols)
- `__init__.py` (py, 0 symbols)
- `intelligence_prompts.py` (py, 1 symbols)
- `manager.py` (py, 22 symbols)
- `test_parsers_properties.py` (py, 1 symbols)
- `test_system_app_sources_properties.py` (py, 6 symbols)
- `test_event_bus.py` (py, 6 symbols)
- `test_keyless_sources.py` (py, 7 symbols)
- `test_opsec_contact.py` (py, 14 symbols)
- `test_pagination.py` (py, 38 symbols)
- ... and 4 more files

### estorides_core: hypothesis_engine (Cohesion: 0.66)

**19 files** in this community:

- `change_detection.py` (py, 20 symbols)
- `fusion_analytics.py` (py, 14 symbols)
- `fusion_store.py` (py, 18 symbols)
- `hypothesis_engine.py` (py, 23 symbols)
- `ids.py` (py, 1 symbols)
- `recon_fusion.py` (py, 19 symbols)
- `reliability_scoring.py` (py, 16 symbols)
- `test_change_detection_properties.py` (py, 8 symbols)
- `test_hypothesis_engine_properties.py` (py, 9 symbols)
- `test_recon_fusion_properties.py` (py, 17 symbols)
- `test_reliability_scoring_properties.py` (py, 12 symbols)
- `test_change_detection.py` (py, 43 symbols)
- `test_fusion_analytics.py` (py, 36 symbols)
- `test_hypothesis_engine.py` (py, 35 symbols)
- `test_ids.py` (py, 7 symbols)
- `test_probabilistic_fusion.py` (py, 27 symbols)
- `test_recon_fusion.py` (py, 33 symbols)
- `test_reliability_scoring.py` (py, 70 symbols)
- `test_ui_professional.py` (py, 39 symbols)

### estorides_core: estorides (Cohesion: 0.45)

**17 files** in this community:

- `estorides_cli.py` (py, 31 symbols)
- `alerter.py` (py, 15 symbols)
- `case_crypto.py` (py, 4 symbols)
- `cases.py` (py, 21 symbols)
- `monitoring.py` (py, 26 symbols)
- `scope.py` (py, 39 symbols)
- `sqlite_store.py` (py, 8 symbols)
- `report.py` (py, 6 symbols)
- `estorides.js` (js, 180 symbols)
- `test_case_crypto.py` (py, 5 symbols)
- `test_cli_watch.py` (py, 16 symbols)
- `test_hardening.py` (py, 17 symbols)
- `test_monitoring.py` (py, 35 symbols)
- `test_obs_fts.py` (py, 3 symbols)
- `test_scope.py` (py, 15 symbols)
- `test_security_remediation.py` (py, 67 symbols)
- `test_sqlite_store.py` (py, 14 symbols)

### estorides_core: config (Cohesion: 0.37)

**17 files** in this community:

- `async_client.py` (py, 21 symbols)
- `config.py` (py, 29 symbols)
- `feeds.py` (py, 16 symbols)
- `intel_resolver.py` (py, 26 symbols)
- `lan_bundle.py` (py, 8 symbols)
- `observation_models.py` (py, 14 symbols)
- `ontology.py` (py, 25 symbols)
- `osiris_sources.py` (py, 8 symbols)
- `pivot_engine.py` (py, 25 symbols)
- `ssrf_guard.py` (py, 12 symbols)
- `transforms.py` (py, 24 symbols)
- `test_observation_models_properties.py` (py, 7 symbols)
- `test_async_client.py` (py, 15 symbols)
- `test_config_env.py` (py, 13 symbols)
- `test_observation_models.py` (py, 28 symbols)
- `test_structured_extraction.py` (py, 8 symbols)
- `test_transforms.py` (py, 12 symbols)

### estorides_core: graph_bundle (Cohesion: 0.68)

**15 files** in this community:

- `__init__.py` (py, 0 symbols)
- `graph_bundle.py` (py, 18 symbols)
- `graph_force.py` (py, 12 symbols)
- `lan_inventory.py` (py, 12 symbols)
- `test_graph_bundle_properties.py` (py, 6 symbols)
- `test_graph_bundle.py` (py, 30 symbols)
- `test_graph_force3d.py` (py, 9 symbols)
- `test_lan_bundle.py` (py, 9 symbols)
- `test_lan_inventory.py` (py, 14 symbols)
- `test_lan_policy.py` (py, 8 symbols)
- `test_lan_scope.py` (py, 8 symbols)
- `test_lan_web.py` (py, 12 symbols)
- `test_orchestrator_fanout.py` (py, 9 symbols)
- `test_parsers.py` (py, 9 symbols)
- `test_retry_policy.py` (py, 2 symbols)

### estorides_core: people_intel (Cohesion: 1.00)

**15 files** in this community:

- `cloud_asset_discovery.py` (py, 7 symbols)
- `code_exposure.py` (py, 10 symbols)
- `pdns_monitor.py` (py, 11 symbols)
- `people_intel.py` (py, 13 symbols)
- `recon_pipeline.py` (py, 1 symbols)
- `supply_chain.py` (py, 13 symbols)
- `tech_fingerprint.py` (py, 6 symbols)
- `vuln_correlation.py` (py, 11 symbols)
- `test_cloud_asset_discovery.py` (py, 18 symbols)
- `test_code_exposure.py` (py, 21 symbols)
- `test_pdns_monitor.py` (py, 17 symbols)
- `test_people_intel.py` (py, 18 symbols)
- `test_supply_chain.py` (py, 17 symbols)
- `test_tech_fingerprint.py` (py, 16 symbols)
- `test_vuln_correlation.py` (py, 17 symbols)

### estorides_export (Cohesion: 0.46)

**15 files** in this community:

- `entity_extraction.py` (py, 21 symbols)
- `entity_resolution.py` (py, 35 symbols)
- `entity_store.py` (py, 5 symbols)
- `knowledge_graph.py` (py, 18 symbols)
- `transliteration.py` (py, 4 symbols)
- `__init__.py` (py, 0 symbols)
- `encryption.py` (py, 4 symbols)
- `misp.py` (py, 3 symbols)
- `recon_report.py` (py, 11 symbols)
- `stix.py` (py, 4 symbols)
- `test_encrypted_export.py` (py, 9 symbols)
- `test_entity_extraction.py` (py, 7 symbols)
- `test_entity_resolution.py` (py, 68 symbols)
- `test_query_intent.py` (py, 6 symbols)
- `test_recon_report.py` (py, 19 symbols)

### estorides_core: tool_install (Cohesion: 0.46)

**12 files** in this community:

- `active_recon.py` (py, 25 symbols)
- `lan_runner.py` (py, 8 symbols)
- `tool_install.py` (py, 27 symbols)
- `tool_runner.py` (py, 15 symbols)
- `validation.py` (py, 6 symbols)
- `test_tool_runner_properties.py` (py, 3 symbols)
- `test_active_recon.py` (py, 24 symbols)
- `test_central_config.py` (py, 3 symbols)
- `test_lan_runner.py` (py, 14 symbols)
- `test_tool_doctor.py` (py, 4 symbols)
- `test_tool_install.py` (py, 28 symbols)
- `test_tool_runner.py` (py, 29 symbols)

### estorides_core: source_health_monitoring (Cohesion: 1.00)

**3 files** in this community:

- `source_health_monitoring.py` (py, 14 symbols)
- `test_source_health_monitoring_properties.py` (py, 7 symbols)
- `test_source_health_monitoring.py` (py, 52 symbols)

---

## Surprising Connections

Files in different communities connected through 3+ indirect hops.

- `lan_inventory.py` <-> `test_change_detection_properties.py` (7 hops, across 4 communities)
- `lan_inventory.py` <-> `test_hypothesis_engine_properties.py` (7 hops, across 4 communities)
- `test_change_detection_properties.py` <-> `test_graph_bundle_properties.py` (7 hops, across 3 communities)
- `test_change_detection_properties.py` <-> `test_recon_report.py` (7 hops, across 3 communities)
- `test_graph_bundle_properties.py` <-> `test_hypothesis_engine_properties.py` (7 hops, across 3 communities)

---

## Suggested Questions

Auto-generated exploration prompts based on graph structure:

- What does config.py depend on, and what depends on it? (43 connections)
- What does estorides_web.py depend on, and what depends on it? (37 connections)
- What does orchestrator.py depend on, and what depends on it? (28 connections)
- How are the 27 files in 'estorides_core: estorides_web' related to each other?
- Why are lan_inventory.py and test_change_detection_properties.py connected through 7 hops across 4 communities?

---

## Force-Graph Explorer

Heterogeneous explorer payload (files, communities, layers, externals) rendered with force-graph physics, stable family colors, log2 node sizing, and convex-hull community overlays. Open `readmenator-maps/graph-force.html` (linked from the maps gallery) or run `explorer`.

- Nodes: 280 (community: 10, external: 73, file: 191, layer: 6)
- Edges: 1659

---

## Corpus Analytics

Attribution funnel and distributions for the explorer dashboard.

- Files: 191 | With symbols: 171 | Attributed: 164 | God nodes: 10 | Symbols: 3183

- Layer `testing`: 90 files
- Layer `utility`: 87 files
- Layer `business_logic`: 5 files
- Layer `data_access`: 3 files
- Layer `infrastructure`: 3 files
- Layer `presentation`: 3 files

- Language `py`: 185 files
- Language `js`: 4 files
- Language `sh`: 2 files

---

## Taint Propagation Map

Taint analysis traces how dangerous imports propagate through the codebase via transitive dependencies. Source files import dangerous modules directly; sink files receive the danger indirectly.

**Taint Sources:** 4 | **Taint Sinks:** 7 | **Propagation Paths:** 20

- `alerter.py` imports `urllib.request` (0 hop to `alerter.py`) [medium]
  Path: alerter.py
- `alerter.py` imports `urllib.request` (1 hop to `ssrf_guard.py`) [medium]
  Path: alerter.py -> ssrf_guard.py
- `alerter.py` imports `urllib.request` (0 hop to `alerter.py`) [medium]
  Path: alerter.py
- `alerter.py` imports `urllib.request` (1 hop to `ssrf_guard.py`) [medium]
  Path: alerter.py -> ssrf_guard.py
- `async_client.py` imports `requests` (0 hop to `async_client.py`) [medium]
  Path: async_client.py
- `async_client.py` imports `requests` (1 hop to `ssrf_guard.py`) [medium]
  Path: async_client.py -> ssrf_guard.py
- `async_client.py` imports `requests` (1 hop to `config.py`) [medium]
  Path: async_client.py -> config.py
- `feeds.py` imports `requests` (0 hop to `feeds.py`) [medium]
  Path: feeds.py
- `feeds.py` imports `requests` (1 hop to `ssrf_guard.py`) [medium]
  Path: feeds.py -> ssrf_guard.py
- `feeds.py` imports `requests` (1 hop to `config.py`) [medium]
  Path: feeds.py -> config.py
- `feeds.py` imports `requests` (0 hop to `feeds.py`) [medium]
  Path: feeds.py
- `feeds.py` imports `requests` (1 hop to `ssrf_guard.py`) [medium]
  Path: feeds.py -> ssrf_guard.py
- `feeds.py` imports `requests` (1 hop to `config.py`) [medium]
  Path: feeds.py -> config.py
- `feeds.py` imports `requests` (0 hop to `feeds.py`) [medium]
  Path: feeds.py
- `feeds.py` imports `requests` (1 hop to `ssrf_guard.py`) [medium]
  Path: feeds.py -> ssrf_guard.py
- `feeds.py` imports `requests` (1 hop to `config.py`) [medium]
  Path: feeds.py -> config.py
- `intel_resolver.py` imports `requests` (0 hop to `intel_resolver.py`) [medium]
  Path: intel_resolver.py
- `intel_resolver.py` imports `requests` (1 hop to `ontology.py`) [medium]
  Path: intel_resolver.py -> ontology.py
- `intel_resolver.py` imports `requests` (1 hop to `ssrf_guard.py`) [medium]
  Path: intel_resolver.py -> ssrf_guard.py
- `intel_resolver.py` imports `requests` (1 hop to `config.py`) [medium]
  Path: intel_resolver.py -> config.py

---

## Hotspot Analysis

Files ranked by combined complexity (symbol count) and centrality (connection count). High-scoring files are architecturally critical and may need refactoring attention.

| File | Complexity | Centrality | Combined | Symbols | Connections |
|------|-----------|------------|----------|---------|-------------|
| `test_security_remediation.py` | 0.372 | 0.034 | 0.169 | 67 | 56 |
| `ids.py` | 0.006 | 0.005 | 0.005 | 1 | 8 |
| `intelligence_prompts.py` | 0.006 | 0.002 | 0.004 | 1 | 4 |
| `openapi.py` | 0.006 | 0.002 | 0.003 | 1 | 3 |
| `test_graph_bundle_assets.py` | 0.006 | 0.002 | 0.003 | 1 | 3 |
| `config.py` | 0.161 | 0.037 | 0.087 | 29 | 62 |
| `ssrf_guard.py` | 0.067 | 0.010 | 0.032 | 12 | 16 |
| `alerter.py` | 0.083 | 0.015 | 0.042 | 15 | 25 |
| `sync_docs.py` | 0.011 | 0.004 | 0.007 | 2 | 7 |
| `test_csp_safe_styles_properties.py` | 0.017 | 0.004 | 0.009 | 3 | 7 |
| `estorides.js` | 1.000 | 1.000 | 1.000 | 180 | 1674 |
| `graph_force.js` | 0.606 | 0.453 | 0.514 | 109 | 758 |
| `graph_bundle.js` | 0.344 | 0.351 | 0.348 | 62 | 588 |
| `estorides_web.py` | 0.539 | 0.072 | 0.259 | 97 | 120 |
| `test_target_management.py` | 0.478 | 0.002 | 0.193 | 86 | 4 |

---

## Concept Graph

Semantic second-brain layer: nouns are concept nodes, verbs are edges. Each noun maps atomically to a file set (EXTRACTED); each verb aggregates structural imports, calls, and inherits into consumes, invokes, extends, depends_on, or bridges (INFERRED).

**50 concepts, 100 relations.**

| Concept | Files | Mentions |
|---------|-------|----------|
| `estorides` | 126 | 260 |
| `when` | 71 | 144 |
| `not` | 69 | 142 |
| `run` | 68 | 186 |
| `returns` | 67 | 193 |
| `source` | 66 | 339 |
| `dict` | 57 | 153 |
| `empty` | 57 | 119 |
| `list` | 56 | 146 |
| `bdd` | 56 | 63 |
| `one` | 55 | 145 |
| `never` | 55 | 126 |
| `only` | 53 | 99 |
| `single` | 52 | 91 |
| `none` | 51 | 104 |
| `every` | 48 | 98 |
| `sources` | 46 | 140 |
| `error` | 46 | 108 |
| `spec` | 46 | 89 |
| `entity` | 45 | 239 |
| `default` | 44 | 81 |
| `type` | 43 | 136 |
| `return` | 43 | 121 |
| `value` | 43 | 105 |
| `result` | 42 | 104 |
| `safe` | 42 | 65 |
| `same` | 41 | 97 |
| `all` | 41 | 71 |
| `module` | 41 | 49 |
| `name` | 40 | 129 |

### Verb Edges

| Source | Verb | Target | Strength | Evidence |
|--------|------|--------|----------|----------|
| `run` | `depends_on` | `estorides` | 1.00 | 10 |
| `returns` | `depends_on` | `estorides` | 0.95 | 10 |
| `when` | `depends_on` | `estorides` | 0.93 | 10 |
| `not` | `depends_on` | `estorides` | 0.92 | 10 |
| `list` | `depends_on` | `estorides` | 0.83 | 10 |
| `sources` | `depends_on` | `estorides` | 0.82 | 10 |
| `estorides` | `depends_on` | `not` | 0.81 | 10 |
| `estorides` | `depends_on` | `single` | 0.81 | 10 |
| `one` | `depends_on` | `estorides` | 0.80 | 10 |
| `estorides` | `depends_on` | `source` | 0.79 | 10 |
| `only` | `depends_on` | `estorides` | 0.78 | 10 |
| `entity` | `depends_on` | `estorides` | 0.77 | 10 |
| `source` | `depends_on` | `estorides` | 0.77 | 10 |
| `estorides` | `depends_on` | `can` | 0.75 | 10 |
| `estorides` | `depends_on` | `list` | 0.75 | 10 |
| `estorides` | `depends_on` | `one` | 0.75 | 10 |
| `estorides` | `depends_on` | `every` | 0.73 | 10 |
| `estorides` | `depends_on` | `returns` | 0.73 | 10 |
| `none` | `depends_on` | `estorides` | 0.73 | 10 |
| `type` | `depends_on` | `estorides` | 0.73 | 10 |
| `estorides` | `depends_on` | `entity` | 0.71 | 10 |
| `estorides` | `depends_on` | `when` | 0.71 | 10 |
| `estorides` | `depends_on` | `return` | 0.70 | 10 |
| `run` | `depends_on` | `single` | 0.69 | 10 |
| `run` | `depends_on` | `not` | 0.68 | 10 |
| `run` | `depends_on` | `source` | 0.68 | 10 |
| `estorides` | `depends_on` | `never` | 0.67 | 10 |
| `estorides` | `depends_on` | `only` | 0.67 | 10 |
| `every` | `depends_on` | `estorides` | 0.67 | 10 |
| `default` | `depends_on` | `estorides` | 0.66 | 10 |

### Dialectic Prompts

- Thesis: `all` centralizes 41 files; Antithesis: `list` pulls 56 files with 24 shared (Jaccard 0.33); Synthesis: should they merge, split by layer, or keep `bridges` explicit?
- Thesis: `all` centralizes 41 files; Antithesis: `none` pulls 51 files with 25 shared (Jaccard 0.37); Synthesis: should they merge, split by layer, or keep `bridges` explicit?
- Thesis: `all` centralizes 41 files; Antithesis: `returns` pulls 67 files with 25 shared (Jaccard 0.30); Synthesis: should they merge, split by layer, or keep `bridges` explicit?
- Thesis: `all` centralizes 41 files; Antithesis: `source` pulls 66 files with 25 shared (Jaccard 0.30); Synthesis: should they merge, split by layer, or keep `bridges` explicit?
- Thesis: `all` centralizes 41 files; Antithesis: `sources` pulls 46 files with 22 shared (Jaccard 0.34); Synthesis: should they merge, split by layer, or keep `bridges` explicit?
- Thesis: `all` centralizes 41 files; Antithesis: `when` pulls 71 files with 26 shared (Jaccard 0.30); Synthesis: should they merge, split by layer, or keep `bridges` explicit?
- Thesis: `any` centralizes 32 files; Antithesis: `can` pulls 40 files with 25 shared (Jaccard 0.53); Synthesis: should they merge, split by layer, or keep `bridges` explicit?
- Thesis: `any` centralizes 32 files; Antithesis: `data` pulls 36 files with 16 shared (Jaccard 0.31); Synthesis: should they merge, split by layer, or keep `bridges` explicit?
- Thesis: `any` centralizes 32 files; Antithesis: `each` pulls 40 files with 17 shared (Jaccard 0.31); Synthesis: should they merge, split by layer, or keep `bridges` explicit?
- Thesis: `any` centralizes 32 files; Antithesis: `every` pulls 48 files with 24 shared (Jaccard 0.43); Synthesis: should they merge, split by layer, or keep `bridges` explicit?

---

## Dependency Cycles

Circular dependencies detected in the resolved import graph. Cycles increase coupling and make refactoring harder.

| Cycle | Length | Files |
|-------|--------|-------|
| `__init__.py -> encryption.py -> __init__.py` | 2 | 2 |
| `__init__.py -> encryption.py -> __init__.py` | 2 | 2 |
| `estorides_web.py -> estorides_web_tools.py -> estorides_web.py` | 2 | 2 |

---

## Change Impact Analysis

Files sorted by how many other files would be affected if they changed. High-impact files should be changed with caution.

| File | Direct Dependents | Transitive Dependents | Total Impact |
|------|------------------|----------------------|--------------|
| `config.py` | 43 | 8 | 51 |
| `entity_extraction.py` | 13 | 36 | 49 |
| `ids.py` | 6 | 31 | 37 |
| `reliability_scoring.py` | 8 | 27 | 35 |
| `sqlite_store.py` | 5 | 27 | 32 |
| `ssrf_guard.py` | 7 | 24 | 31 |
| `tool_runner.py` | 10 | 21 | 31 |
| `transliteration.py` | 2 | 24 | 26 |
| `entity_resolution.py` | 4 | 21 | 25 |
| `knowledge_graph.py` | 7 | 18 | 25 |
| `__init__.py` | 13 | 11 | 24 |
| `case_crypto.py` | 2 | 22 | 24 |
| `parsers.py` | 6 | 18 | 24 |
| `cases.py` | 7 | 16 | 23 |
| `ontology.py` | 2 | 21 | 23 |

---

## Architecture Violations

Violations of architectural layer rules detected in the import graph. **17 strict violations, 0 warnings.**

| Source | Source Layer | Target | Target Layer | Description | Severity |
|--------|-------------|--------|-------------|-------------|----------|
| `estorides_web.py` | presentation | `fusion_store.py` | data_access | presentation must not import data_access | strict |
| `test_csp_safe_styles_properties.py` | testing | `web_security.py` | presentation | testing must not import presentation | strict |
| `test_auth_gate.py` | testing | `web_security.py` | presentation | testing must not import presentation | strict |
| `test_auth_gate.py` | testing | `web_security.py` | presentation | testing must not import presentation | strict |
| `test_csp_safe_styles.py` | testing | `web_security.py` | presentation | testing must not import presentation | strict |
| `test_csp_safe_styles.py` | testing | `web_security.py` | presentation | testing must not import presentation | strict |
| `test_graph_rag_search.py` | testing | `estorides_web.py` | presentation | testing must not import presentation | strict |
| `test_hardening.py` | testing | `web_security.py` | presentation | testing must not import presentation | strict |
| `test_map_basemap.py` | testing | `web_security.py` | presentation | testing must not import presentation | strict |
| `test_openapi.py` | testing | `estorides_web.py` | presentation | testing must not import presentation | strict |
| `test_security_remediation.py` | testing | `web_security.py` | presentation | testing must not import presentation | strict |
| `test_web_helpers.py` | testing | `web_security.py` | presentation | testing must not import presentation | strict |
| `test_web_helpers.py` | testing | `estorides_web.py` | presentation | testing must not import presentation | strict |
| `test_web_tools_blueprint.py` | testing | `estorides_web.py` | presentation | testing must not import presentation | strict |
| `test_web_tools_blueprint.py` | testing | `estorides_web.py` | presentation | testing must not import presentation | strict |
| `test_web_tools_blueprint.py` | testing | `estorides_web.py` | presentation | testing must not import presentation | strict |
| `test_web_tools_blueprint.py` | testing | `estorides_web.py` | presentation | testing must not import presentation | strict |

---

## Suggested Linting Rules

Automatically suggested linting and security rules based on patterns detected in the codebase. These can be exported as Semgrep rules using the `--export-rules` flag.

| Rule ID | Severity | Description | Language | Matches |
|---------|----------|-------------|----------|---------|
| `RM001` | info | Large number of functions in py: 2263 total | py | 2263 |
| `RM002` | info | Large number of functions in js: 365 total | js | 365 |
| `RM003` | info | Print statement found (consider logging instead) | python | 150 |

---

## Orphans

Files with no documentation or low connectivity. These are candidates for documentation investment or cleanup.

- `gb_shots.py` (0 symbols, no doc)
- `gfv2_bridge.py` (0 symbols, no doc)
- `gfv2_click.py` (0 symbols, no doc)
- `gfv2_data.py` (0 symbols, no doc)
- `gfv2_debug.py` (0 symbols, no doc)
- `gfv2_dom.py` (0 symbols, no doc)
- `gfv2_func.py` (0 symbols, no doc)
- `gfv2_raw.py` (0 symbols, no doc)
- `gfv2_shots.py` (0 symbols, no doc)
- `gfv2_step.py` (0 symbols, no doc)
- `gfv2_time.py` (0 symbols, no doc)
- `_multi_test.sh` (0 symbols, no doc)
- `active_recon.py` (25 symbols, no doc)
- `cloud_asset_discovery.py` (7 symbols, no doc)
- `code_exposure.py` (10 symbols, no doc)
- `pdns_monitor.py` (11 symbols, no doc)
- `people_intel.py` (13 symbols, no doc)
- `recon_pipeline.py` (1 symbols, no doc)
- `supply_chain.py` (13 symbols, no doc)
- `tech_fingerprint.py` (6 symbols, no doc)
- *... and 5 more*

---

## Query Recipes

Example queries you can run against this knowledge base using the ranking engine:

```
# Find files most relevant to a concept
readmenator query "Where is the import resolver implemented?"

# Rank files by relevance to a topic
readmenator query "How does documentation generation work?"

# Explain why a file ranks highly
readmenator query "explain readmenator/_documentation.py"

# Trace dependency paths with ranked context
readmenator query "path from CLI to exporter"
```

The ranking model uses the following signals:

- **Personalized PageRank** (45% weight): query-specific relevance via seed propagation
- **Global Authority** (20% weight): structural importance via standard PageRank
- **Test Coverage** (15% weight): fraction of symbols referenced in test files
- **Doc Coverage** (10% weight): presence of docstrings and file-level docs
- **Freshness** (10% weight): recent modification activity

Results include score decomposition and justification paths for each ranked item.

---

## Structural Knowledge Map

> **Note:** The visual graph below has been intelligently pruned to the top 300 most relevant nodes to prevent rendering crashes. Full details of all files are documented in the Architecture Reference.

```mermaid
graph TD
    classDef mod fill:#1e1e1e,stroke:#ff6666,stroke-width:2px,color:#fff;
    classDef cls fill:#2d2d2d,stroke:#4ec9b0,stroke-width:2px,color:#fff;
    classDef fn fill:#333,stroke:#dcdcaa,stroke-width:1px,color:#dcdcaa;
    classDef ext fill:#111,stroke:#666,stroke-dasharray:5 5,color:#aaa;
    subgraph community_3 ["estorides_core: estorides"]
    static_js_estorides_js["estorides.js (js)"]
    class static_js_estorides_js mod;
    static_js_estorides_js_setVisible["setVisible"]
    class static_js_estorides_js_setVisible fn;
    static_js_estorides_js --> static_js_estorides_js_setVisible
    static_js_estorides_js_isLanQueryLocal["isLanQueryLocal"]
    class static_js_estorides_js_isLanQueryLocal fn;
    static_js_estorides_js --> static_js_estorides_js_isLanQueryLocal
    static_js_estorides_js_lanRunFields["lanRunFields"]
    class static_js_estorides_js_lanRunFields fn;
    static_js_estorides_js --> static_js_estorides_js_lanRunFields
    static_js_estorides_js_ensureLanConsent["ensureLanConsent"]
    class static_js_estorides_js_ensureLanConsent fn;
    static_js_estorides_js --> static_js_estorides_js_ensureLanConsent
    static_js_estorides_js_detectQueryTypeLocal["detectQueryTypeLocal"]
    class static_js_estorides_js_detectQueryTypeLocal fn;
    static_js_estorides_js --> static_js_estorides_js_detectQueryTypeLocal
    static_js_graph_force_js["graph_force.js (js)"]
    class static_js_graph_force_js mod;
    static_js_graph_bundle_js["graph_bundle.js (js)"]
    class static_js_graph_bundle_js mod;
    static_js_source_manager_js["source_manager.js (js)"]
    class static_js_source_manager_js mod;
    end
    subgraph community_0 ["estorides_core: estorides_web"]
    estorides_web_py["estorides_web.py (py)"]
    class estorides_web_py mod;
    estorides_cli_py["estorides_cli.py (py)"]
    class estorides_cli_py mod;
    tests_test_security_remediation_py["test_security_remediation.py (py)"]
    class tests_test_security_remediation_py mod;
    end
    subgraph community_1 ["estorides_core: parsers"]
    estorides_core_orchestrator_py["orchestrator.py (py)"]
    class estorides_core_orchestrator_py mod;
    tests_test_system_app_sources_py["test_system_app_sources.py (py)"]
    class tests_test_system_app_sources_py mod;
    end
    subgraph community_5 ["estorides_core: graph_bundle"]
    tests_test_graph_bundle_py["test_graph_bundle.py (py)"]
    class tests_test_graph_bundle_py mod;
    end
    subgraph community_4 ["estorides_core: config"]
    estorides_core_async_client_py["async_client.py (py)"]
    class estorides_core_async_client_py mod;
    estorides_core_system_app_sources_py["system_app_sources.py (py)"]
    class estorides_core_system_app_sources_py mod;
    estorides_core_discoverer_py["discoverer.py (py)"]
    class estorides_core_discoverer_py mod;
    end
    subgraph community_8 ["estorides_core: tool_install"]
    estorides_core_tool_install_py["tool_install.py (py)"]
    class estorides_core_tool_install_py mod;
    estorides_core_monitoring_py["monitoring.py (py)"]
    class estorides_core_monitoring_py mod;
    estorides_core_ontology_py["ontology.py (py)"]
    class estorides_core_ontology_py mod;
    estorides_core_feeds_py["feeds.py (py)"]
    class estorides_core_feeds_py mod;
    tests_test_socmint_py["test_socmint.py (py)"]
    class tests_test_socmint_py mod;
    estorides_core_intel_resolver_py["intel_resolver.py (py)"]
    class estorides_core_intel_resolver_py mod;
    end
    subgraph community_2 ["estorides_core: hypothesis_engine"]
    estorides_core_fusion_store_py["fusion_store.py (py)"]
    class estorides_core_fusion_store_py mod;
    tests_test_config_env_py["test_config_env.py (py)"]
    class tests_test_config_env_py mod;
    tests_properties_test_reliability_scoring_properties_py["test_reliability_scoring_properties.py (py)"]
    class tests_properties_test_reliability_scoring_properties_py mod;
    end
    subgraph community_6 ["estorides_core: people_intel"]
    estorides_core_recon_pipeline_py["recon_pipeline.py (py)"]
    class estorides_core_recon_pipeline_py mod;
    tests_test_transforms_py["test_transforms.py (py)"]
    class tests_test_transforms_py mod;
    estorides_core_alerter_py["alerter.py (py)"]
    class estorides_core_alerter_py mod;
    end
    subgraph community_7 ["estorides_export"]
    tests_test_encrypted_export_py["test_encrypted_export.py (py)"]
    class tests_test_encrypted_export_py mod;
    tests_test_ops_observability_py["test_ops_observability.py (py)"]
    class tests_test_ops_observability_py mod;
    estorides_core_web_security_py["web_security.py (py)"]
    class estorides_core_web_security_py mod;
    estorides_core_cases_py["cases.py (py)"]
    class estorides_core_cases_py mod;
    estorides_core_recon_fusion_py["recon_fusion.py (py)"]
    class estorides_core_recon_fusion_py mod;
    estorides_core_osiris_sources_py["osiris_sources.py (py)"]
    class estorides_core_osiris_sources_py mod;
    estorides_core_entity_resolution_py["entity_resolution.py (py)"]
    class estorides_core_entity_resolution_py mod;
    tests_test_monitoring_py["test_monitoring.py (py)"]
    class tests_test_monitoring_py mod;
    estorides_core_transforms_py["transforms.py (py)"]
    class estorides_core_transforms_py mod;
    estorides_core_knowledge_graph_py["knowledge_graph.py (py)"]
    class estorides_core_knowledge_graph_py mod;
    tests_test_cli_watch_py["test_cli_watch.py (py)"]
    class tests_test_cli_watch_py mod;
    estorides_core_audit_py["audit.py (py)"]
    class estorides_core_audit_py mod;
    estorides_core_lan_bundle_py["lan_bundle.py (py)"]
    class estorides_core_lan_bundle_py mod;
    estorides_web_tools_py["estorides_web_tools.py (py)"]
    class estorides_web_tools_py mod;
    estorides_core_hypothesis_engine_py["hypothesis_engine.py (py)"]
    class estorides_core_hypothesis_engine_py mod;
    tests_test_hardening_py["test_hardening.py (py)"]
    class tests_test_hardening_py mod;
    tests_test_csp_safe_styles_py["test_csp_safe_styles.py (py)"]
    class tests_test_csp_safe_styles_py mod;
    tests_properties_test_system_app_sources_properties_py["test_system_app_sources_properties.py (py)"]
    class tests_properties_test_system_app_sources_properties_py mod;
    tests_test_event_bus_py["test_event_bus.py (py)"]
    class tests_test_event_bus_py mod;
    estorides_export_encryption_py["encryption.py (py)"]
    class estorides_export_encryption_py mod;
    tests_test_entity_resolution_py["test_entity_resolution.py (py)"]
    class tests_test_entity_resolution_py mod;
    estorides_llm_manager_py["manager.py (py)"]
    class estorides_llm_manager_py mod;
    estorides_core_tool_runner_py["tool_runner.py (py)"]
    class estorides_core_tool_runner_py mod;
    estorides_core_graph_kuzu_py["graph_kuzu.py (py)"]
    class estorides_core_graph_kuzu_py mod;
    estorides_export_stix_py["stix.py (py)"]
    class estorides_export_stix_py mod;
    estorides_export_misp_py["misp.py (py)"]
    class estorides_export_misp_py mod;
    tests_test_central_config_py["test_central_config.py (py)"]
    class tests_test_central_config_py mod;
    tests_test_ui_professional_py["test_ui_professional.py (py)"]
    class tests_test_ui_professional_py mod;
    tests_test_fusion_analytics_py["test_fusion_analytics.py (py)"]
    class tests_test_fusion_analytics_py mod;
    estorides_core_config_py["config.py (py)"]
    class estorides_core_config_py mod;
    tests_test_tool_runner_py["test_tool_runner.py (py)"]
    class tests_test_tool_runner_py mod;
    tests_test_keyless_sources_py["test_keyless_sources.py (py)"]
    class tests_test_keyless_sources_py mod;
    estorides_core_entity_store_py["entity_store.py (py)"]
    class estorides_core_entity_store_py mod;
    tests_test_case_crypto_py["test_case_crypto.py (py)"]
    class tests_test_case_crypto_py mod;
    estorides_export___init___py["__init__.py (py)"]
    class estorides_export___init___py mod;
    estorides_core_scope_py["scope.py (py)"]
    class estorides_core_scope_py mod;
    estorides_core_pivot_engine_py["pivot_engine.py (py)"]
    class estorides_core_pivot_engine_py mod;
    estorides_core_entity_extraction_py["entity_extraction.py (py)"]
    class estorides_core_entity_extraction_py mod;
    estorides_core_source_loader_py["source_loader.py (py)"]
    class estorides_core_source_loader_py mod;
    estorides_core_ops_observability_py["ops_observability.py (py)"]
    class estorides_core_ops_observability_py mod;
    tests_test_auth_gate_py["test_auth_gate.py (py)"]
    class tests_test_auth_gate_py mod;
    estorides_core_lan_runner_py["lan_runner.py (py)"]
    class estorides_core_lan_runner_py mod;
    tests_test_structured_extraction_py["test_structured_extraction.py (py)"]
    class tests_test_structured_extraction_py mod;
    tests_test_tool_doctor_py["test_tool_doctor.py (py)"]
    class tests_test_tool_doctor_py mod;
    tests_test_web_tools_blueprint_py["test_web_tools_blueprint.py (py)"]
    class tests_test_web_tools_blueprint_py mod;
    estorides_core_change_detection_py["change_detection.py (py)"]
    class estorides_core_change_detection_py mod;
    estorides_core_graph_bundle_py["graph_bundle.py (py)"]
    class estorides_core_graph_bundle_py mod;
    estorides_core_ssrf_guard_py["ssrf_guard.py (py)"]
    class estorides_core_ssrf_guard_py mod;
    tests_test_graph_rag_search_py["test_graph_rag_search.py (py)"]
    class tests_test_graph_rag_search_py mod;
    estorides_core_sqlite_store_py["sqlite_store.py (py)"]
    class estorides_core_sqlite_store_py mod;
    tests_properties_test_graph_bundle_properties_py["test_graph_bundle_properties.py (py)"]
    class tests_properties_test_graph_bundle_properties_py mod;
    estorides_core_graph_rag_search_py["graph_rag_search.py (py)"]
    class estorides_core_graph_rag_search_py mod;
    tests_test_probabilistic_fusion_py["test_probabilistic_fusion.py (py)"]
    class tests_test_probabilistic_fusion_py mod;
    tests_test_search_telemetry_py["test_search_telemetry.py (py)"]
    class tests_test_search_telemetry_py mod;
    tests_properties_test_recon_fusion_properties_py["test_recon_fusion_properties.py (py)"]
    class tests_properties_test_recon_fusion_properties_py mod;
    tests_test_async_client_py["test_async_client.py (py)"]
    class tests_test_async_client_py mod;
    tests_test_opsec_contact_py["test_opsec_contact.py (py)"]
    class tests_test_opsec_contact_py mod;
    estorides_core_lan_inventory_py["lan_inventory.py (py)"]
    class estorides_core_lan_inventory_py mod;
    tests_test_lan_bundle_py["test_lan_bundle.py (py)"]
    class tests_test_lan_bundle_py mod;
    tests_test_orchestrator_fanout_py["test_orchestrator_fanout.py (py)"]
    class tests_test_orchestrator_fanout_py mod;
    tests_properties_test_change_detection_properties_py["test_change_detection_properties.py (py)"]
    class tests_properties_test_change_detection_properties_py mod;
    estorides_core_validation_py["validation.py (py)"]
    class estorides_core_validation_py mod;
    tests_test_source_routing_py["test_source_routing.py (py)"]
    class tests_test_source_routing_py mod;
    estorides_core_lan_scope_py["lan_scope.py (py)"]
    class estorides_core_lan_scope_py mod;
    tests_properties_test_csp_safe_styles_properties_py["test_csp_safe_styles_properties.py (py)"]
    class tests_properties_test_csp_safe_styles_properties_py mod;
    tests_test_retry_policy_py["test_retry_policy.py (py)"]
    class tests_test_retry_policy_py mod;
    tools_sync_docs_py["sync_docs.py (py)"]
    class tools_sync_docs_py mod;
    tests_test_reliability_scoring_py["test_reliability_scoring.py (py)"]
    class tests_test_reliability_scoring_py mod;
    estorides_core_parsers_py["parsers.py (py)"]
    class estorides_core_parsers_py mod;
    tests_test_change_detection_py["test_change_detection.py (py)"]
    class tests_test_change_detection_py mod;
    tests_test_hypothesis_engine_py["test_hypothesis_engine.py (py)"]
    class tests_test_hypothesis_engine_py mod;
    tests_test_recon_fusion_py["test_recon_fusion.py (py)"]
    class tests_test_recon_fusion_py mod;
    estorides_core_active_recon_py["active_recon.py (py)"]
    class estorides_core_active_recon_py mod;
    estorides_core_search_telemetry_py["search_telemetry.py (py)"]
    class estorides_core_search_telemetry_py mod;
    tests_test_recon_report_py["test_recon_report.py (py)"]
    class tests_test_recon_report_py mod;
    end
    subgraph community_9 ["estorides_core: source_health_monitoring"]
    estorides_core_source_health_monitoring_py["source_health_monitoring.py (py)"]
    class estorides_core_source_health_monitoring_py mod;
    tests_test_sqlite_store_py["test_sqlite_store.py (py)"]
    class tests_test_sqlite_store_py mod;
    tests_test_web_helpers_py["test_web_helpers.py (py)"]
    class tests_test_web_helpers_py mod;
    estorides_core_socmint_py["socmint.py (py)"]
    class estorides_core_socmint_py mod;
    estorides_export_recon_report_py["recon_report.py (py)"]
    class estorides_export_recon_report_py mod;
    tests_properties_test_hypothesis_engine_properties_py["test_hypothesis_engine_properties.py (py)"]
    class tests_properties_test_hypothesis_engine_properties_py mod;
    tests_test_audit_log_py["test_audit_log.py (py)"]
    class tests_test_audit_log_py mod;
    estorides_core_case_crypto_py["case_crypto.py (py)"]
    class estorides_core_case_crypto_py mod;
    tests_properties_test_tool_runner_properties_py["test_tool_runner_properties.py (py)"]
    class tests_properties_test_tool_runner_properties_py mod;
    tests_properties_test_parsers_properties_py["test_parsers_properties.py (py)"]
    class tests_properties_test_parsers_properties_py mod;
    _scratchpad_gfv2_shots_py["gfv2_shots.py (py)"]
    class _scratchpad_gfv2_shots_py mod;
    tests_test_source_health_monitoring_py["test_source_health_monitoring.py (py)"]
    class tests_test_source_health_monitoring_py mod;
    tests_test_observation_models_py["test_observation_models.py (py)"]
    class tests_test_observation_models_py mod;
    tests_test_tool_install_py["test_tool_install.py (py)"]
    class tests_test_tool_install_py mod;
    tests_test_active_recon_py["test_active_recon.py (py)"]
    class tests_test_active_recon_py mod;
    tests_test_cloud_asset_discovery_py["test_cloud_asset_discovery.py (py)"]
    class tests_test_cloud_asset_discovery_py mod;
    tests_test_pdns_monitor_py["test_pdns_monitor.py (py)"]
    class tests_test_pdns_monitor_py mod;
    estorides_core_reliability_scoring_py["reliability_scoring.py (py)"]
    class estorides_core_reliability_scoring_py mod;
    estorides_core_observation_models_py["observation_models.py (py)"]
    class estorides_core_observation_models_py mod;
    estorides_core_people_intel_py["people_intel.py (py)"]
    class estorides_core_people_intel_py mod;
    estorides_core_supply_chain_py["supply_chain.py (py)"]
    class estorides_core_supply_chain_py mod;
    estorides_core_vuln_correlation_py["vuln_correlation.py (py)"]
    class estorides_core_vuln_correlation_py mod;
    estorides_core_code_exposure_py["code_exposure.py (py)"]
    class estorides_core_code_exposure_py mod;
    estorides_core_event_bus_py["event_bus.py (py)"]
    class estorides_core_event_bus_py mod;
    estorides_core_lan_policy_py["lan_policy.py (py)"]
    class estorides_core_lan_policy_py mod;
    tests_test_graph_force3d_py["test_graph_force3d.py (py)"]
    class tests_test_graph_force3d_py mod;
    tests_test_job_registry_py["test_job_registry.py (py)"]
    class tests_test_job_registry_py mod;
    tests_properties_test_observation_models_properties_py["test_observation_models_properties.py (py)"]
    class tests_properties_test_observation_models_properties_py mod;
    tests_properties_test_source_health_monitoring_properties_py["test_source_health_monitoring_properties.py (py)"]
    class tests_properties_test_source_health_monitoring_properties_py mod;
    estorides_core_tech_fingerprint_py["tech_fingerprint.py (py)"]
    class estorides_core_tech_fingerprint_py mod;
    estorides_export_report_py["report.py (py)"]
    class estorides_export_report_py mod;
    tests_properties_test_search_telemetry_properties_py["test_search_telemetry_properties.py (py)"]
    class tests_properties_test_search_telemetry_properties_py mod;
    tests_test_map_basemap_py["test_map_basemap.py (py)"]
    class tests_test_map_basemap_py mod;
    tests_test_obs_fts_py["test_obs_fts.py (py)"]
    class tests_test_obs_fts_py mod;
    _scratchpad_gfv2_click_py["gfv2_click.py (py)"]
    class _scratchpad_gfv2_click_py mod;
    _scratchpad_gfv2_data_py["gfv2_data.py (py)"]
    class _scratchpad_gfv2_data_py mod;
    wsgi_py["wsgi.py (py)"]
    class wsgi_py mod;
    tests_test_target_management_py["test_target_management.py (py)"]
    class tests_test_target_management_py mod;
    tests_test_people_intel_py["test_people_intel.py (py)"]
    class tests_test_people_intel_py mod;
    estorides_core_fusion_analytics_py["fusion_analytics.py (py)"]
    class estorides_core_fusion_analytics_py mod;
    tests_test_lan_runner_py["test_lan_runner.py (py)"]
    class tests_test_lan_runner_py mod;
    tests_test_source_loader_py["test_source_loader.py (py)"]
    class tests_test_source_loader_py mod;
    estorides_core_pdns_monitor_py["pdns_monitor.py (py)"]
    class estorides_core_pdns_monitor_py mod;
    estorides_core_job_registry_py["job_registry.py (py)"]
    class estorides_core_job_registry_py mod;
    tests_properties_test_target_management_properties_py["test_target_management_properties.py (py)"]
    class tests_properties_test_target_management_properties_py mod;
    tests_test_parsers_py["test_parsers.py (py)"]
    class tests_test_parsers_py mod;
    estorides_core_cloud_asset_discovery_py["cloud_asset_discovery.py (py)"]
    class estorides_core_cloud_asset_discovery_py mod;
    tests_test_ids_py["test_ids.py (py)"]
    class tests_test_ids_py mod;
    estorides_core_lan_web_py["lan_web.py (py)"]
    class estorides_core_lan_web_py mod;
    estorides_core_mitre_attack_py["mitre_attack.py (py)"]
    class estorides_core_mitre_attack_py mod;
    tools_split_sources_py["split_sources.py (py)"]
    class tools_split_sources_py mod;
    _scratchpad_gb_shots_py["gb_shots.py (py)"]
    class _scratchpad_gb_shots_py mod;
    _scratchpad_gfv2_bridge_py["gfv2_bridge.py (py)"]
    class _scratchpad_gfv2_bridge_py mod;
    _scratchpad_gfv2_debug_py["gfv2_debug.py (py)"]
    class _scratchpad_gfv2_debug_py mod;
    _scratchpad_gfv2_dom_py["gfv2_dom.py (py)"]
    class _scratchpad_gfv2_dom_py mod;
    _scratchpad_gfv2_func_py["gfv2_func.py (py)"]
    class _scratchpad_gfv2_func_py mod;
    _scratchpad_gfv2_raw_py["gfv2_raw.py (py)"]
    class _scratchpad_gfv2_raw_py mod;
    _scratchpad_gfv2_step_py["gfv2_step.py (py)"]
    class _scratchpad_gfv2_step_py mod;
    _scratchpad_gfv2_time_py["gfv2_time.py (py)"]
    class _scratchpad_gfv2_time_py mod;
    app_py["app.py (py)"]
    class app_py mod;
    web_py["web.py (py)"]
    class web_py mod;
    tests_test_pagination_py["test_pagination.py (py)"]
    class tests_test_pagination_py mod;
    tests_test_code_exposure_py["test_code_exposure.py (py)"]
    class tests_test_code_exposure_py mod;
    tests_test_supply_chain_py["test_supply_chain.py (py)"]
    class tests_test_supply_chain_py mod;
    tests_test_vuln_correlation_py["test_vuln_correlation.py (py)"]
    class tests_test_vuln_correlation_py mod;
    estorides_core_relationship_inference_py["relationship_inference.py (py)"]
    class estorides_core_relationship_inference_py mod;
    tests_test_tech_fingerprint_py["test_tech_fingerprint.py (py)"]
    class tests_test_tech_fingerprint_py mod;
    tests_test_scope_py["test_scope.py (py)"]
    class tests_test_scope_py mod;
    estorides_core_graph_force_py["graph_force.py (py)"]
    class estorides_core_graph_force_py mod;
    estorides_core_pagination_py["pagination.py (py)"]
    class estorides_core_pagination_py mod;
    tests_test_entity_extraction_py["test_entity_extraction.py (py)"]
    class tests_test_entity_extraction_py mod;
    tests_test_query_intent_py["test_query_intent.py (py)"]
    class tests_test_query_intent_py mod;
    estorides_core_transliteration_py["transliteration.py (py)"]
    class estorides_core_transliteration_py mod;
    estorides_llm_intelligence_prompts_py["intelligence_prompts.py (py)"]
    class estorides_llm_intelligence_prompts_py mod;
    tests_test_graph_bundle_assets_py["test_graph_bundle_assets.py (py)"]
    class tests_test_graph_bundle_assets_py mod;
    tests_test_openapi_py["test_openapi.py (py)"]
    class tests_test_openapi_py mod;
    tests_conftest_py["conftest.py (py)"]
    class tests_conftest_py mod;
    tests_test_target_scoring_py["test_target_scoring.py (py)"]
    class tests_test_target_scoring_py mod;
    tests_test_lan_inventory_py["test_lan_inventory.py (py)"]
    class tests_test_lan_inventory_py mod;
    tests_test_envutil_py["test_envutil.py (py)"]
    class tests_test_envutil_py mod;
    tests_test_lan_web_py["test_lan_web.py (py)"]
    class tests_test_lan_web_py mod;
    tests_test_ui_visibility_py["test_ui_visibility.py (py)"]
    class tests_test_ui_visibility_py mod;
    tests_test_lan_policy_py["test_lan_policy.py (py)"]
    class tests_test_lan_policy_py mod;
    tests_test_lan_scope_py["test_lan_scope.py (py)"]
    class tests_test_lan_scope_py mod;
    estorides_core_ids_py["ids.py (py)"]
    class estorides_core_ids_py mod;
    estorides_core_openapi_py["openapi.py (py)"]
    class estorides_core_openapi_py mod;
    estorides_core___init___py["__init__.py (py)"]
    class estorides_core___init___py mod;
    estorides_llm___init___py["__init__.py (py)"]
    class estorides_llm___init___py mod;
    tests_properties_conftest_py["conftest.py (py)"]
    class tests_properties_conftest_py mod;
    install_sh["install.sh (sh)"]
    class install_sh mod;
    _multi_test_sh["_multi_test.sh (sh)"]
    class _multi_test_sh mod;
    end
    app_py -- resolved_imports --> wsgi_py
    estorides_cli_py -- resolved_imports --> estorides_core_config_py
    estorides_cli_py -- resolved_imports --> estorides_core_knowledge_graph_py
    estorides_cli_py -- resolved_imports --> estorides_core_orchestrator_py
    estorides_cli_py -- resolved_imports --> estorides_core_validation_py
    estorides_cli_py -- resolved_imports --> estorides_export___init___py
    estorides_cli_py -- resolved_imports --> estorides_core_monitoring_py
    estorides_cli_py -- resolved_imports --> estorides_core_cases_py
    estorides_cli_py -- resolved_imports --> estorides_core_discoverer_py
    estorides_cli_py -- resolved_imports --> estorides_core_scope_py
    estorides_cli_py -- resolved_imports --> estorides_core_cases_py
    estorides_cli_py -- resolved_imports --> estorides_export_report_py
    estorides_cli_py -- resolved_imports --> estorides_core_cases_py
    estorides_cli_py -- resolved_imports --> estorides_core_fusion_store_py
    estorides_cli_py -- resolved_imports --> estorides_core_entity_extraction_py
    estorides_cli_py -- resolved_imports --> estorides_core_monitoring_py
    estorides_cli_py -- resolved_imports --> estorides_core_monitoring_py
    estorides_cli_py -- resolved_imports --> estorides_core_monitoring_py
    estorides_cli_py -- resolved_imports --> estorides_core_monitoring_py
    estorides_cli_py -- resolved_imports --> estorides_core_monitoring_py
    estorides_cli_py -- resolved_imports --> estorides_core_monitoring_py
    estorides_cli_py -- resolved_imports --> estorides_core_alerter_py
    estorides_cli_py -- resolved_imports --> estorides_core_alerter_py
    estorides_cli_py -- resolved_imports --> estorides_core_monitoring_py
    estorides_cli_py -- resolved_imports --> estorides_core_monitoring_py
    estorides_cli_py -- resolved_imports --> estorides_core_monitoring_py
    estorides_cli_py -- resolved_imports --> estorides_web_py
    estorides_cli_py -- resolved_imports --> estorides_core_entity_extraction_py
    estorides_core___init___py -- resolved_imports --> estorides_core_config_py
    estorides_core_active_recon_py -- resolved_imports --> estorides_core_tool_runner_py
    estorides_core_alerter_py -- resolved_imports --> estorides_core_ssrf_guard_py
    estorides_core_async_client_py -- resolved_imports --> estorides_core_config_py
    estorides_core_async_client_py -- resolved_imports --> estorides_core_ssrf_guard_py
    estorides_core_async_client_py -- resolved_imports --> estorides_core_config_py
    estorides_core_audit_py -- resolved_imports --> estorides_core_config_py
    estorides_core_cases_py -- resolved_imports --> estorides_core_case_crypto_py
    estorides_core_cases_py -- resolved_imports --> estorides_core_config_py
    estorides_core_cases_py -- resolved_imports --> estorides_core_sqlite_store_py
    estorides_core_change_detection_py -- resolved_imports --> estorides_core_ids_py
    estorides_core_change_detection_py -- resolved_imports --> estorides_core_reliability_scoring_py
    estorides_core_discoverer_py -- resolved_imports --> estorides_core_cases_py
    estorides_core_discoverer_py -- resolved_imports --> estorides_core_config_py
    estorides_core_discoverer_py -- resolved_imports --> estorides_core_job_registry_py
    estorides_core_discoverer_py -- resolved_imports --> estorides_core_pivot_engine_py
    estorides_core_discoverer_py -- resolved_imports --> estorides_core_graph_kuzu_py
    estorides_core_discoverer_py -- resolved_imports --> estorides_core_orchestrator_py
    estorides_core_discoverer_py -- resolved_imports --> estorides_core_orchestrator_py
    estorides_core_entity_extraction_py -- resolved_imports --> estorides_core_config_py
    estorides_core_entity_resolution_py -- resolved_imports --> estorides_core_config_py
    estorides_core_entity_resolution_py -- resolved_imports --> estorides_core_entity_extraction_py
    estorides_core_entity_resolution_py -- resolved_imports --> estorides_core_ids_py
    estorides_core_entity_resolution_py -- resolved_imports --> estorides_core_transliteration_py
    estorides_core_entity_store_py -- resolved_imports --> estorides_core_config_py
    estorides_core_entity_store_py -- resolved_imports --> estorides_core_entity_resolution_py
    estorides_core_entity_store_py -- resolved_imports --> estorides_core_sqlite_store_py
    estorides_core_feeds_py -- resolved_imports --> estorides_core_config_py
    estorides_core_feeds_py -- resolved_imports --> estorides_core_ssrf_guard_py
    estorides_core_fusion_store_py -- resolved_imports --> estorides_core_config_py
    estorides_core_fusion_store_py -- resolved_imports --> estorides_core_ids_py
    estorides_core_fusion_store_py -- resolved_imports --> estorides_core_reliability_scoring_py
    estorides_core_fusion_store_py -- resolved_imports --> estorides_core_sqlite_store_py
    estorides_core_fusion_store_py -- resolved_imports --> estorides_core_entity_resolution_py
    estorides_core_graph_bundle_py -- resolved_imports --> estorides_core_graph_force_py
    estorides_core_graph_kuzu_py -- resolved_imports --> estorides_core_config_py
    estorides_core_hypothesis_engine_py -- resolved_imports --> estorides_core_ids_py
    estorides_core_hypothesis_engine_py -- resolved_imports --> estorides_core_reliability_scoring_py
    estorides_core_hypothesis_engine_py -- resolved_imports --> estorides_core_reliability_scoring_py
    estorides_core_intel_resolver_py -- resolved_imports --> estorides_core_config_py
    estorides_core_intel_resolver_py -- resolved_imports --> estorides_core_ssrf_guard_py
    estorides_core_intel_resolver_py -- resolved_imports --> estorides_core_ontology_py
    estorides_core_knowledge_graph_py -- resolved_imports --> estorides_core_config_py
    estorides_core_knowledge_graph_py -- resolved_imports --> estorides_core_entity_extraction_py
    estorides_core_lan_bundle_py -- resolved_imports --> estorides_core_config_py
    estorides_core_lan_runner_py -- resolved_imports --> estorides_core_tool_runner_py
    estorides_core_monitoring_py -- resolved_imports --> estorides_core_config_py
    estorides_core_monitoring_py -- resolved_imports --> estorides_core_sqlite_store_py
    estorides_core_observation_models_py -- resolved_imports --> estorides_core_config_py
    estorides_core_ontology_py -- resolved_imports --> estorides_core_config_py
    estorides_core_ontology_py -- resolved_imports --> estorides_core_ssrf_guard_py
    estorides_core_ops_observability_py -- resolved_imports --> estorides_core_config_py
    estorides_core_orchestrator_py -- resolved_imports --> estorides_llm___init___py
    estorides_core_orchestrator_py -- resolved_imports --> estorides_core_async_client_py
    estorides_core_orchestrator_py -- resolved_imports --> estorides_core_config_py
    estorides_core_orchestrator_py -- resolved_imports --> estorides_core_entity_extraction_py
    estorides_core_orchestrator_py -- resolved_imports --> estorides_core_event_bus_py
    estorides_core_orchestrator_py -- resolved_imports --> estorides_core_knowledge_graph_py
    estorides_core_orchestrator_py -- resolved_imports --> estorides_core_mitre_attack_py
    estorides_core_orchestrator_py -- resolved_imports --> estorides_core_ontology_py
    estorides_core_orchestrator_py -- resolved_imports --> estorides_core_parsers_py
    estorides_core_orchestrator_py -- resolved_imports --> estorides_core_recon_fusion_py
    estorides_core_orchestrator_py -- resolved_imports --> estorides_core_relationship_inference_py
    estorides_core_orchestrator_py -- resolved_imports --> estorides_core_source_loader_py
    estorides_core_orchestrator_py -- resolved_imports --> estorides_core_system_app_sources_py
    estorides_core_orchestrator_py -- resolved_imports --> estorides_core_graph_kuzu_py
    estorides_core_orchestrator_py -- resolved_imports --> estorides_core_cases_py
    estorides_core_orchestrator_py -- resolved_imports --> estorides_core_intel_resolver_py
    estorides_core_orchestrator_py -- resolved_imports --> estorides_core_entity_resolution_py
    estorides_core_orchestrator_py -- resolved_imports --> estorides_core_fusion_store_py
    estorides_core_orchestrator_py -- resolved_imports --> estorides_core_fusion_store_py
    estorides_core_orchestrator_py -- resolved_imports --> estorides_core_entity_store_py
    ext_time["time"]
    class ext_time ext;
    _scratchpad_gb_shots_py -.->|imports| ext_time
    ext_selenium["selenium"]
    class ext_selenium ext;
    _scratchpad_gb_shots_py -.->|imports| ext_selenium
    ext_selenium_webdriver_chrome_service["selenium.webdriver.chrome.service"]
    class ext_selenium_webdriver_chrome_service ext;
    _scratchpad_gb_shots_py -.->|imports| ext_selenium_webdriver_chrome_service
    ext_selenium_webdriver_common_by["selenium.webdriver.common.by"]
    class ext_selenium_webdriver_common_by ext;
    _scratchpad_gb_shots_py -.->|imports| ext_selenium_webdriver_common_by
    _scratchpad_gfv2_bridge_py -.->|imports| ext_time
    _scratchpad_gfv2_bridge_py -.->|imports| ext_selenium
    _scratchpad_gfv2_bridge_py -.->|imports| ext_selenium_webdriver_common_by
    _scratchpad_gfv2_bridge_py -.->|imports| ext_selenium_webdriver_chrome_service
    _scratchpad_gfv2_click_py -.->|imports| ext_time
    _scratchpad_gfv2_click_py -.->|imports| ext_selenium
    _scratchpad_gfv2_click_py -.->|imports| ext_selenium_webdriver_common_by
    _scratchpad_gfv2_click_py -.->|imports| ext_selenium_webdriver_chrome_service
    ext_selenium_webdriver_common_action_chains["selenium.webdriver.common.action_chains"]
    class ext_selenium_webdriver_common_action_chains ext;
    _scratchpad_gfv2_click_py -.->|imports| ext_selenium_webdriver_common_action_chains
    _scratchpad_gfv2_data_py -.->|imports| ext_time
    ext_json["json"]
    class ext_json ext;
    _scratchpad_gfv2_data_py -.->|imports| ext_json
    _scratchpad_gfv2_data_py -.->|imports| ext_selenium
    _scratchpad_gfv2_data_py -.->|imports| ext_selenium_webdriver_common_by
    _scratchpad_gfv2_data_py -.->|imports| ext_selenium_webdriver_chrome_service
    _scratchpad_gfv2_debug_py -.->|imports| ext_time
    _scratchpad_gfv2_debug_py -.->|imports| ext_selenium
    _scratchpad_gfv2_debug_py -.->|imports| ext_selenium_webdriver_common_by
    _scratchpad_gfv2_debug_py -.->|imports| ext_selenium_webdriver_chrome_service
    _scratchpad_gfv2_dom_py -.->|imports| ext_time
    _scratchpad_gfv2_dom_py -.->|imports| ext_selenium
    _scratchpad_gfv2_dom_py -.->|imports| ext_selenium_webdriver_common_by
    _scratchpad_gfv2_dom_py -.->|imports| ext_selenium_webdriver_chrome_service
    _scratchpad_gfv2_func_py -.->|imports| ext_time
    _scratchpad_gfv2_func_py -.->|imports| ext_selenium
    _scratchpad_gfv2_func_py -.->|imports| ext_selenium_webdriver_common_by
    _scratchpad_gfv2_func_py -.->|imports| ext_selenium_webdriver_chrome_service
    _scratchpad_gfv2_raw_py -.->|imports| ext_time
    _scratchpad_gfv2_raw_py -.->|imports| ext_selenium
    _scratchpad_gfv2_raw_py -.->|imports| ext_selenium_webdriver_common_by
    _scratchpad_gfv2_raw_py -.->|imports| ext_selenium_webdriver_chrome_service
    _scratchpad_gfv2_shots_py -.->|imports| ext_time
    _scratchpad_gfv2_shots_py -.->|imports| ext_json
    _scratchpad_gfv2_shots_py -.->|imports| ext_selenium
    _scratchpad_gfv2_shots_py -.->|imports| ext_selenium_webdriver_chrome_service
    _scratchpad_gfv2_shots_py -.->|imports| ext_selenium_webdriver_common_by
    _scratchpad_gfv2_shots_py -.->|imports| ext_selenium_webdriver_common_action_chains
    _scratchpad_gfv2_step_py -.->|imports| ext_time
    _scratchpad_gfv2_step_py -.->|imports| ext_selenium
    _scratchpad_gfv2_step_py -.->|imports| ext_selenium_webdriver_common_by
    _scratchpad_gfv2_step_py -.->|imports| ext_selenium_webdriver_chrome_service
    _scratchpad_gfv2_time_py -.->|imports| ext_time
    _scratchpad_gfv2_time_py -.->|imports| ext_selenium
    _scratchpad_gfv2_time_py -.->|imports| ext_selenium_webdriver_common_by
    _scratchpad_gfv2_time_py -.->|imports| ext_selenium_webdriver_chrome_service
    ext___future__["__future__"]
    class ext___future__ ext;
    app_py -.->|imports| ext___future__
    ext_warnings["warnings"]
    class ext_warnings ext;
    app_py -.->|imports| ext_warnings
    ext_wsgi["wsgi"]
    class ext_wsgi ext;
    app_py -.->|imports| ext_wsgi
    estorides_cli_py -.->|imports| ext___future__
    ext_argparse["argparse"]
    class ext_argparse ext;
    estorides_cli_py -.->|imports| ext_argparse
    ext_asyncio["asyncio"]
    class ext_asyncio ext;
    estorides_cli_py -.->|imports| ext_asyncio
    estorides_cli_py -.->|imports| ext_json
    ext_logging["logging"]
    class ext_logging ext;
    estorides_cli_py -.->|imports| ext_logging
    ext_sys["sys"]
    class ext_sys ext;
    estorides_cli_py -.->|imports| ext_sys
    estorides_cli_py -.->|imports| ext_time
    ext_collections_abc["collections.abc"]
    class ext_collections_abc ext;
    estorides_cli_py -.->|imports| ext_collections_abc
    ext_pathlib["pathlib"]
    class ext_pathlib ext;
    estorides_cli_py -.->|imports| ext_pathlib
    ext_typing["typing"]
    class ext_typing ext;
    estorides_cli_py -.->|imports| ext_typing
    ext_estorides_core_config["estorides_core.config"]
    class ext_estorides_core_config ext;
    estorides_cli_py -.->|imports| ext_estorides_core_config
    ext_estorides_core_knowledge_graph["estorides_core.knowledge_graph"]
    class ext_estorides_core_knowledge_graph ext;
    estorides_cli_py -.->|imports| ext_estorides_core_knowledge_graph
    ext_estorides_core_orchestrator["estorides_core.orchestrator"]
    class ext_estorides_core_orchestrator ext;
    estorides_cli_py -.->|imports| ext_estorides_core_orchestrator
    ext_estorides_core_validation["estorides_core.validation"]
    class ext_estorides_core_validation ext;
    estorides_cli_py -.->|imports| ext_estorides_core_validation
    ext_estorides_export["estorides_export"]
    class ext_estorides_export ext;
    estorides_cli_py -.->|imports| ext_estorides_export
    ext_estorides_core_monitoring["estorides_core.monitoring"]
    class ext_estorides_core_monitoring ext;
    estorides_cli_py -.->|imports| ext_estorides_core_monitoring
    ext_estorides_core_cases["estorides_core.cases"]
    class ext_estorides_core_cases ext;
    estorides_cli_py -.->|imports| ext_estorides_core_cases
    ext_estorides_core_discoverer["estorides_core.discoverer"]
    class ext_estorides_core_discoverer ext;
    estorides_cli_py -.->|imports| ext_estorides_core_discoverer
    estorides_cli_py -.->|imports| ext_sys
    ext_estorides_core_scope["estorides_core.scope"]
    class ext_estorides_core_scope ext;
    estorides_cli_py -.->|imports| ext_estorides_core_scope
    ext_networkx["networkx"]
    class ext_networkx ext;
    estorides_cli_py -.->|imports| ext_networkx
    estorides_cli_py -.->|imports| ext_networkx
    estorides_cli_py -.->|imports| ext_estorides_core_cases
    ext_estorides_export_report["estorides_export.report"]
    class ext_estorides_export_report ext;
    estorides_cli_py -.->|imports| ext_estorides_export_report
    estorides_cli_py -.->|imports| ext_estorides_core_cases
    ext_estorides_core_fusion_store["estorides_core.fusion_store"]
    class ext_estorides_core_fusion_store ext;
    estorides_cli_py -.->|imports| ext_estorides_core_fusion_store
    ext_estorides_core_entity_extraction["estorides_core.entity_extraction"]
    class ext_estorides_core_entity_extraction ext;
    estorides_cli_py -.->|imports| ext_estorides_core_entity_extraction
    estorides_cli_py -.->|imports| ext_estorides_core_monitoring
    estorides_cli_py -.->|imports| ext_estorides_core_monitoring
    estorides_cli_py -.->|imports| ext_estorides_core_monitoring
    estorides_cli_py -.->|imports| ext_estorides_core_monitoring
    estorides_cli_py -.->|imports| ext_estorides_core_monitoring
    estorides_cli_py -.->|imports| ext_estorides_core_monitoring
    ext_estorides_core_alerter["estorides_core.alerter"]
    class ext_estorides_core_alerter ext;
    estorides_cli_py -.->|imports| ext_estorides_core_alerter
    estorides_cli_py -.->|imports| ext_estorides_core_alerter
    estorides_cli_py -.->|imports| ext_estorides_core_monitoring
    estorides_cli_py -.->|imports| ext_estorides_core_monitoring
    estorides_cli_py -.->|imports| ext_estorides_core_monitoring
    ext_estorides_web["estorides_web"]
    class ext_estorides_web ext;
    estorides_cli_py -.->|imports| ext_estorides_web
    estorides_cli_py -.->|imports| ext_estorides_core_entity_extraction
    estorides_cli_py -.->|imports| ext_networkx
    estorides_cli_py -.->|imports| ext_time
    ext_config["config"]
    class ext_config ext;
    estorides_core___init___py -.->|imports| ext_config
    estorides_core_active_recon_py -.->|imports| ext___future__
    estorides_core_active_recon_py -.->|imports| ext_logging
    ext_dataclasses["dataclasses"]
    class ext_dataclasses ext;
    estorides_core_active_recon_py -.->|imports| ext_dataclasses
    estorides_core_active_recon_py -.->|imports| ext_typing
    ext_tool_runner["tool_runner"]
    class ext_tool_runner ext;
    estorides_core_active_recon_py -.->|imports| ext_tool_runner
    estorides_core_alerter_py -.->|imports| ext___future__
    estorides_core_alerter_py -.->|imports| ext_json
    estorides_core_alerter_py -.->|imports| ext_logging
    ext_os["os"]
    class ext_os ext;
    estorides_core_alerter_py -.->|imports| ext_os
    ext_smtplib["smtplib"]
    class ext_smtplib ext;
    estorides_core_alerter_py -.->|imports| ext_smtplib
    estorides_core_alerter_py -.->|imports| ext_time
    ext_urllib_error["urllib.error"]
    class ext_urllib_error ext;
    estorides_core_alerter_py -.->|imports| ext_urllib_error
    ext_urllib_request["urllib.request"]
    class ext_urllib_request ext;
    estorides_core_alerter_py -.->|imports| ext_urllib_request
    ext_datetime["datetime"]
    class ext_datetime ext;
    estorides_core_alerter_py -.->|imports| ext_datetime
    ext_email_mime_text["email.mime.text"]
    class ext_email_mime_text ext;
    estorides_core_alerter_py -.->|imports| ext_email_mime_text
    estorides_core_alerter_py -.->|imports| ext_typing
    estorides_core_alerter_py -.->|imports| ext_urllib_request
    ext_ssrf_guard["ssrf_guard"]
    class ext_ssrf_guard ext;
    estorides_core_alerter_py -.->|imports| ext_ssrf_guard
    estorides_core_alerter_py -.->|imports| ext_datetime
    estorides_core_async_client_py -.->|imports| ext___future__
    estorides_core_async_client_py -.->|imports| ext_asyncio
    ext_hashlib["hashlib"]
    class ext_hashlib ext;
    estorides_core_async_client_py -.->|imports| ext_hashlib
    estorides_core_async_client_py -.->|imports| ext_json
    estorides_core_async_client_py -.->|imports| ext_logging
    ext_sqlite3["sqlite3"]
    class ext_sqlite3 ext;
    estorides_core_async_client_py -.->|imports| ext_sqlite3
    estorides_core_async_client_py -.->|imports| ext_time
    estorides_core_async_client_py -.->|imports| ext_collections_abc
    ext_contextlib["contextlib"]
    class ext_contextlib ext;
    estorides_core_async_client_py -.->|imports| ext_contextlib
    estorides_core_async_client_py -.->|imports| ext_dataclasses
    estorides_core_async_client_py -.->|imports| ext_pathlib
    estorides_core_async_client_py -.->|imports| ext_typing
    ext_urllib_parse["urllib.parse"]
    class ext_urllib_parse ext;
    estorides_core_async_client_py -.->|imports| ext_urllib_parse
    ext_aiohttp["aiohttp"]
    class ext_aiohttp ext;
    estorides_core_async_client_py -.->|imports| ext_aiohttp
    estorides_core_async_client_py -.->|imports| ext_config
    estorides_core_async_client_py -.->|imports| ext_ssrf_guard
    ext_requests["requests"]
    class ext_requests ext;
    estorides_core_async_client_py -.->|imports| ext_requests
    estorides_core_async_client_py -.->|imports| ext_config
    ext_aiohttp_socks["aiohttp_socks"]
    class ext_aiohttp_socks ext;
    estorides_core_async_client_py -.->|imports| ext_aiohttp_socks
    estorides_core_audit_py -.->|imports| ext___future__
    estorides_core_audit_py -.->|imports| ext_json
    estorides_core_audit_py -.->|imports| ext_logging
    estorides_core_audit_py -.->|imports| ext_os
    ext_threading["threading"]
    class ext_threading ext;
    estorides_core_audit_py -.->|imports| ext_threading
    estorides_core_audit_py -.->|imports| ext_time
    ext_collections["collections"]
    class ext_collections ext;
    estorides_core_audit_py -.->|imports| ext_collections
    estorides_core_audit_py -.->|imports| ext_dataclasses
    estorides_core_audit_py -.->|imports| ext_datetime
    estorides_core_audit_py -.->|imports| ext_pathlib
    estorides_core_audit_py -.->|imports| ext_typing
    estorides_core_audit_py -.->|imports| ext_config
    estorides_core_case_crypto_py -.->|imports| ext___future__
    estorides_core_case_crypto_py -.->|imports| ext_logging
    estorides_core_case_crypto_py -.->|imports| ext_os
    estorides_core_case_crypto_py -.->|imports| ext_typing
    ext_cryptography_fernet["cryptography.fernet"]
    class ext_cryptography_fernet ext;
    estorides_core_case_crypto_py -.->|imports| ext_cryptography_fernet
    estorides_core_case_crypto_py -.->|imports| ext_cryptography_fernet
    estorides_core_cases_py -.->|imports| ext___future__
    estorides_core_cases_py -.->|imports| ext_json
    estorides_core_cases_py -.->|imports| ext_logging
    estorides_core_cases_py -.->|imports| ext_sqlite3
    estorides_core_cases_py -.->|imports| ext_time
    ext_uuid["uuid"]
    class ext_uuid ext;
    estorides_core_cases_py -.->|imports| ext_uuid
    estorides_core_cases_py -.->|imports| ext_pathlib
    estorides_core_cases_py -.->|imports| ext_typing
    ext_case_crypto["case_crypto"]
    class ext_case_crypto ext;
    estorides_core_cases_py -.->|imports| ext_case_crypto
    estorides_core_cases_py -.->|imports| ext_config
    ext_sqlite_store["sqlite_store"]
    class ext_sqlite_store ext;
    estorides_core_cases_py -.->|imports| ext_sqlite_store
    estorides_core_change_detection_py -.->|imports| ext___future__
    estorides_core_change_detection_py -.->|imports| ext_collections_abc
    estorides_core_change_detection_py -.->|imports| ext_dataclasses
    estorides_core_change_detection_py -.->|imports| ext_typing
    ext_ids["ids"]
    class ext_ids ext;
    estorides_core_change_detection_py -.->|imports| ext_ids
    ext_reliability_scoring["reliability_scoring"]
    class ext_reliability_scoring ext;
    estorides_core_change_detection_py -.->|imports| ext_reliability_scoring
    estorides_core_cloud_asset_discovery_py -.->|imports| ext___future__
    estorides_core_cloud_asset_discovery_py -.->|imports| ext_logging
    estorides_core_cloud_asset_discovery_py -.->|imports| ext_dataclasses
    estorides_core_cloud_asset_discovery_py -.->|imports| ext_typing
    estorides_core_code_exposure_py -.->|imports| ext___future__
    estorides_core_code_exposure_py -.->|imports| ext_logging
    ext_re["re"]
    class ext_re ext;
    estorides_core_code_exposure_py -.->|imports| ext_re
    estorides_core_code_exposure_py -.->|imports| ext_dataclasses
    estorides_core_code_exposure_py -.->|imports| ext_typing
    estorides_core_config_py -.->|imports| ext___future__
    estorides_core_config_py -.->|imports| ext_os
    estorides_core_config_py -.->|imports| ext_sys
    estorides_core_config_py -.->|imports| ext_collections_abc
    estorides_core_config_py -.->|imports| ext_dataclasses
    estorides_core_config_py -.->|imports| ext_pathlib
    ext_envutil["envutil"]
    class ext_envutil ext;
    estorides_core_config_py -.->|imports| ext_envutil
    estorides_core_config_py -.->|imports| ext_envutil
    estorides_core_config_py -.->|imports| ext_envutil
    estorides_core_config_py -.->|imports| ext_envutil
    estorides_core_discoverer_py -.->|imports| ext___future__
    estorides_core_discoverer_py -.->|imports| ext_asyncio
    estorides_core_discoverer_py -.->|imports| ext_logging
    estorides_core_discoverer_py -.->|imports| ext_time
    estorides_core_discoverer_py -.->|imports| ext_dataclasses
    estorides_core_discoverer_py -.->|imports| ext_typing
    ext_cases["cases"]
    class ext_cases ext;
    estorides_core_discoverer_py -.->|imports| ext_cases
    estorides_core_discoverer_py -.->|imports| ext_config
    ext_job_registry["job_registry"]
    class ext_job_registry ext;
    estorides_core_discoverer_py -.->|imports| ext_job_registry
    ext_pivot_engine["pivot_engine"]
    class ext_pivot_engine ext;
    estorides_core_discoverer_py -.->|imports| ext_pivot_engine
    ext_estorides_core_graph_kuzu["estorides_core.graph_kuzu"]
    class ext_estorides_core_graph_kuzu ext;
    estorides_core_discoverer_py -.->|imports| ext_estorides_core_graph_kuzu
    estorides_core_discoverer_py -.->|imports| ext_estorides_core_orchestrator
    ext_orchestrator["orchestrator"]
    class ext_orchestrator ext;
    estorides_core_discoverer_py -.->|imports| ext_orchestrator
    estorides_core_entity_extraction_py -.->|imports| ext___future__
    estorides_core_entity_extraction_py -.->|imports| ext_json
    estorides_core_entity_extraction_py -.->|imports| ext_re
    estorides_core_entity_extraction_py -.->|imports| ext_dataclasses
    estorides_core_entity_extraction_py -.->|imports| ext_typing
    estorides_core_entity_extraction_py -.->|imports| ext_config
    ext_difflib["difflib"]
    class ext_difflib ext;
    estorides_core_entity_extraction_py -.->|imports| ext_difflib
    estorides_core_entity_extraction_py -.->|imports| ext_urllib_parse
    estorides_core_entity_resolution_py -.->|imports| ext___future__
    ext_ipaddress["ipaddress"]
    class ext_ipaddress ext;
    estorides_core_entity_resolution_py -.->|imports| ext_ipaddress
    estorides_core_entity_resolution_py -.->|imports| ext_re
    estorides_core_entity_resolution_py -.->|imports| ext_dataclasses
    estorides_core_entity_resolution_py -.->|imports| ext_typing
    estorides_core_entity_resolution_py -.->|imports| ext_config
    ext_entity_extraction["entity_extraction"]
    class ext_entity_extraction ext;
    estorides_core_entity_resolution_py -.->|imports| ext_entity_extraction
    estorides_core_entity_resolution_py -.->|imports| ext_ids
    ext_transliteration["transliteration"]
    class ext_transliteration ext;
    estorides_core_entity_resolution_py -.->|imports| ext_transliteration
    estorides_core_entity_store_py -.->|imports| ext___future__
    estorides_core_entity_store_py -.->|imports| ext_time
    estorides_core_entity_store_py -.->|imports| ext_pathlib
    estorides_core_entity_store_py -.->|imports| ext_typing
    estorides_core_entity_store_py -.->|imports| ext_config
    ext_entity_resolution["entity_resolution"]
    class ext_entity_resolution ext;
    estorides_core_entity_store_py -.->|imports| ext_entity_resolution
    estorides_core_entity_store_py -.->|imports| ext_sqlite_store
    estorides_core_event_bus_py -.->|imports| ext___future__
    estorides_core_event_bus_py -.->|imports| ext_logging
    estorides_core_event_bus_py -.->|imports| ext_re
    estorides_core_event_bus_py -.->|imports| ext_collections_abc
    estorides_core_event_bus_py -.->|imports| ext_typing
    estorides_core_feeds_py -.->|imports| ext___future__
    estorides_core_feeds_py -.->|imports| ext_json
    estorides_core_feeds_py -.->|imports| ext_logging
    estorides_core_feeds_py -.->|imports| ext_os
    estorides_core_feeds_py -.->|imports| ext_time
    ext_abc["abc"]
    class ext_abc ext;
    estorides_core_feeds_py -.->|imports| ext_abc
    estorides_core_feeds_py -.->|imports| ext_dataclasses
    estorides_core_feeds_py -.->|imports| ext_pathlib
    estorides_core_feeds_py -.->|imports| ext_typing
    estorides_core_feeds_py -.->|imports| ext_config
    estorides_core_feeds_py -.->|imports| ext_ssrf_guard
    estorides_core_feeds_py -.->|imports| ext_requests
    ext_csv["csv"]
    class ext_csv ext;
    estorides_core_feeds_py -.->|imports| ext_csv
    ext_io["io"]
    class ext_io ext;
    estorides_core_feeds_py -.->|imports| ext_io
    estorides_core_feeds_py -.->|imports| ext_requests
    estorides_core_feeds_py -.->|imports| ext_requests
    estorides_core_fusion_analytics_py -.->|imports| ext___future__
    estorides_core_fusion_analytics_py -.->|imports| ext_logging
    estorides_core_fusion_analytics_py -.->|imports| ext_time
    estorides_core_fusion_analytics_py -.->|imports| ext_typing
    estorides_core_fusion_store_py -.->|imports| ext___future__
    estorides_core_fusion_store_py -.->|imports| ext_json
    estorides_core_fusion_store_py -.->|imports| ext_logging
    estorides_core_fusion_store_py -.->|imports| ext_sqlite3
    estorides_core_fusion_store_py -.->|imports| ext_time
    estorides_core_fusion_store_py -.->|imports| ext_pathlib
    estorides_core_fusion_store_py -.->|imports| ext_typing
    estorides_core_fusion_store_py -.->|imports| ext_config
    estorides_core_fusion_store_py -.->|imports| ext_ids
    estorides_core_fusion_store_py -.->|imports| ext_reliability_scoring
    estorides_core_fusion_store_py -.->|imports| ext_sqlite_store
    estorides_core_fusion_store_py -.->|imports| ext_entity_resolution
    estorides_core_graph_bundle_py -.->|imports| ext___future__
    ext_math["math"]
    class ext_math ext;
    estorides_core_graph_bundle_py -.->|imports| ext_math
    estorides_core_graph_bundle_py -.->|imports| ext_collections_abc
    estorides_core_graph_bundle_py -.->|imports| ext_dataclasses
    estorides_core_graph_bundle_py -.->|imports| ext_typing
    ext_estorides_core_envutil["estorides_core.envutil"]
    class ext_estorides_core_envutil ext;
    estorides_core_graph_bundle_py -.->|imports| ext_estorides_core_envutil
    ext_estorides_core_graph_force["estorides_core.graph_force"]
    class ext_estorides_core_graph_force ext;
    estorides_core_graph_bundle_py -.->|imports| ext_estorides_core_graph_force
    estorides_core_graph_force_py -.->|imports| ext___future__
    estorides_core_graph_force_py -.->|imports| ext_math
    estorides_core_graph_force_py -.->|imports| ext_typing
    estorides_core_graph_kuzu_py -.->|imports| ext___future__
    estorides_core_graph_kuzu_py -.->|imports| ext_json
    estorides_core_graph_kuzu_py -.->|imports| ext_logging
    estorides_core_graph_kuzu_py -.->|imports| ext_os
    estorides_core_graph_kuzu_py -.->|imports| ext_threading
    estorides_core_graph_kuzu_py -.->|imports| ext_time
    estorides_core_graph_kuzu_py -.->|imports| ext_pathlib
    estorides_core_graph_kuzu_py -.->|imports| ext_typing
    ext_kuzu["kuzu"]
    class ext_kuzu ext;
    estorides_core_graph_kuzu_py -.->|imports| ext_kuzu
    estorides_core_graph_kuzu_py -.->|imports| ext_config
    estorides_core_graph_rag_search_py -.->|imports| ext___future__
    estorides_core_graph_rag_search_py -.->|imports| ext_math
    estorides_core_graph_rag_search_py -.->|imports| ext_re
    estorides_core_graph_rag_search_py -.->|imports| ext_collections_abc
    estorides_core_graph_rag_search_py -.->|imports| ext_dataclasses
    estorides_core_graph_rag_search_py -.->|imports| ext_typing
    estorides_core_graph_rag_search_py -.->|imports| ext_estorides_core_envutil
    estorides_core_hypothesis_engine_py -.->|imports| ext___future__
    estorides_core_hypothesis_engine_py -.->|imports| ext_logging
    estorides_core_hypothesis_engine_py -.->|imports| ext_collections
    estorides_core_hypothesis_engine_py -.->|imports| ext_collections_abc
    estorides_core_hypothesis_engine_py -.->|imports| ext_dataclasses
    estorides_core_hypothesis_engine_py -.->|imports| ext_typing
    estorides_core_hypothesis_engine_py -.->|imports| ext_ids
    estorides_core_hypothesis_engine_py -.->|imports| ext_reliability_scoring
    estorides_core_hypothesis_engine_py -.->|imports| ext_reliability_scoring
    estorides_core_ids_py -.->|imports| ext___future__
    estorides_core_ids_py -.->|imports| ext_hashlib
    estorides_core_intel_resolver_py -.->|imports| ext___future__
    estorides_core_intel_resolver_py -.->|imports| ext_ipaddress
    estorides_core_intel_resolver_py -.->|imports| ext_json
    estorides_core_intel_resolver_py -.->|imports| ext_logging
    estorides_core_intel_resolver_py -.->|imports| ext_os
    estorides_core_intel_resolver_py -.->|imports| ext_re
    estorides_core_intel_resolver_py -.->|imports| ext_threading
    estorides_core_intel_resolver_py -.->|imports| ext_time
    estorides_core_intel_resolver_py -.->|imports| ext_collections
    estorides_core_intel_resolver_py -.->|imports| ext_typing
    estorides_core_intel_resolver_py -.->|imports| ext_requests
    estorides_core_intel_resolver_py -.->|imports| ext_config
    estorides_core_intel_resolver_py -.->|imports| ext_ssrf_guard
    ext_ontology["ontology"]
    class ext_ontology ext;
    estorides_core_intel_resolver_py -.->|imports| ext_ontology
    estorides_core_job_registry_py -.->|imports| ext___future__
    estorides_core_job_registry_py -.->|imports| ext_threading
    estorides_core_job_registry_py -.->|imports| ext_time
    estorides_core_job_registry_py -.->|imports| ext_typing
    estorides_core_knowledge_graph_py -.->|imports| ext___future__
    estorides_core_knowledge_graph_py -.->|imports| ext_json
    estorides_core_knowledge_graph_py -.->|imports| ext_logging
    estorides_core_knowledge_graph_py -.->|imports| ext_time
    estorides_core_knowledge_graph_py -.->|imports| ext_collections
    estorides_core_knowledge_graph_py -.->|imports| ext_pathlib
    estorides_core_knowledge_graph_py -.->|imports| ext_typing
    estorides_core_knowledge_graph_py -.->|imports| ext_networkx
    estorides_core_knowledge_graph_py -.->|imports| ext_config
    estorides_core_knowledge_graph_py -.->|imports| ext_entity_extraction
    ext_networkx_algorithms_community["networkx.algorithms.community"]
    class ext_networkx_algorithms_community ext;
    estorides_core_knowledge_graph_py -.->|imports| ext_networkx_algorithms_community
    estorides_core_lan_bundle_py -.->|imports| ext___future__
    estorides_core_lan_bundle_py -.->|imports| ext_json
    estorides_core_lan_bundle_py -.->|imports| ext_logging
    estorides_core_lan_bundle_py -.->|imports| ext_os
    estorides_core_lan_bundle_py -.->|imports| ext_re
    estorides_core_lan_bundle_py -.->|imports| ext_time
    estorides_core_lan_bundle_py -.->|imports| ext_uuid
    estorides_core_lan_bundle_py -.->|imports| ext_dataclasses
    estorides_core_lan_bundle_py -.->|imports| ext_pathlib
    estorides_core_lan_bundle_py -.->|imports| ext_typing
    estorides_core_lan_bundle_py -.->|imports| ext_config
    estorides_core_lan_bundle_py -.->|imports| ext_datetime
    estorides_core_lan_inventory_py -.->|imports| ext___future__
    estorides_core_lan_inventory_py -.->|imports| ext_ipaddress
    estorides_core_lan_inventory_py -.->|imports| ext_logging
    estorides_core_lan_inventory_py -.->|imports| ext_re
    estorides_core_lan_inventory_py -.->|imports| ext_dataclasses
    estorides_core_lan_inventory_py -.->|imports| ext_typing
    ext_defusedxml_ElementTree["defusedxml.ElementTree"]
    class ext_defusedxml_ElementTree ext;
    estorides_core_lan_inventory_py -.->|imports| ext_defusedxml_ElementTree
    estorides_core_lan_policy_py -.->|imports| ext___future__
    estorides_core_lan_policy_py -.->|imports| ext_ipaddress
    estorides_core_lan_policy_py -.->|imports| ext_dataclasses
    estorides_core_lan_policy_py -.->|imports| ext_typing
    estorides_core_lan_policy_py -.->|imports| ext_envutil
    estorides_core_lan_runner_py -.->|imports| ext___future__
    estorides_core_lan_runner_py -.->|imports| ext_logging
    ext_shutil["shutil"]
    class ext_shutil ext;
    estorides_core_lan_runner_py -.->|imports| ext_shutil
    ext_subprocess["subprocess"]
    class ext_subprocess ext;
    estorides_core_lan_runner_py -.->|imports| ext_subprocess
    estorides_core_lan_runner_py -.->|imports| ext_collections_abc
    estorides_core_lan_runner_py -.->|imports| ext_dataclasses
    estorides_core_lan_runner_py -.->|imports| ext_typing
    estorides_core_lan_runner_py -.->|imports| ext_tool_runner
    estorides_core_lan_scope_py -.->|imports| ext___future__
    estorides_core_lan_scope_py -.->|imports| ext_ipaddress
    estorides_core_lan_scope_py -.->|imports| ext_logging
    estorides_core_lan_scope_py -.->|imports| ext_re
    estorides_core_lan_scope_py -.->|imports| ext_shutil
    estorides_core_lan_scope_py -.->|imports| ext_subprocess
    estorides_core_lan_scope_py -.->|imports| ext_typing
    estorides_core_lan_web_py -.->|imports| ext___future__
    estorides_core_lan_web_py -.->|imports| ext_dataclasses
    estorides_core_lan_web_py -.->|imports| ext_typing
    estorides_core_lan_web_py -.->|imports| ext_ipaddress
    estorides_core_mitre_attack_py -.->|imports| ext___future__
    estorides_core_mitre_attack_py -.->|imports| ext_logging
    estorides_core_mitre_attack_py -.->|imports| ext_typing
    estorides_core_mitre_attack_py -.->|imports| ext_json
    estorides_core_monitoring_py -.->|imports| ext___future__
    estorides_core_monitoring_py -.->|imports| ext_asyncio
    estorides_core_monitoring_py -.->|imports| ext_json
    estorides_core_monitoring_py -.->|imports| ext_logging
    estorides_core_monitoring_py -.->|imports| ext_os
    estorides_core_monitoring_py -.->|imports| ext_sqlite3
    estorides_core_monitoring_py -.->|imports| ext_threading
    estorides_core_monitoring_py -.->|imports| ext_time
    estorides_core_monitoring_py -.->|imports| ext_uuid
    estorides_core_monitoring_py -.->|imports| ext_collections_abc
    estorides_core_monitoring_py -.->|imports| ext_dataclasses
    estorides_core_monitoring_py -.->|imports| ext_pathlib
    estorides_core_monitoring_py -.->|imports| ext_typing
    estorides_core_monitoring_py -.->|imports| ext_config
    estorides_core_monitoring_py -.->|imports| ext_sqlite_store
    ext_concurrent_futures["concurrent.futures"]
    class ext_concurrent_futures ext;
    estorides_core_monitoring_py -.->|imports| ext_concurrent_futures
    estorides_core_observation_models_py -.->|imports| ext___future__
    estorides_core_observation_models_py -.->|imports| ext_typing
    ext_pydantic["pydantic"]
    class ext_pydantic ext;
    estorides_core_observation_models_py -.->|imports| ext_pydantic
    estorides_core_observation_models_py -.->|imports| ext_config
    estorides_core_ontology_py -.->|imports| ext___future__
    estorides_core_ontology_py -.->|imports| ext_csv
    estorides_core_ontology_py -.->|imports| ext_io
    estorides_core_ontology_py -.->|imports| ext_json
    estorides_core_ontology_py -.->|imports| ext_logging
    estorides_core_ontology_py -.->|imports| ext_re
    estorides_core_ontology_py -.->|imports| ext_threading
    estorides_core_ontology_py -.->|imports| ext_time
    estorides_core_ontology_py -.->|imports| ext_collections
    estorides_core_ontology_py -.->|imports| ext_dataclasses
    estorides_core_ontology_py -.->|imports| ext_typing
    estorides_core_ontology_py -.->|imports| ext_urllib_parse
    estorides_core_ontology_py -.->|imports| ext_config
    estorides_core_ontology_py -.->|imports| ext_ssrf_guard
    estorides_core_ontology_py -.->|imports| ext_requests
    estorides_core_ontology_py -.->|imports| ext_pathlib
    estorides_core_openapi_py -.->|imports| ext___future__
    estorides_core_openapi_py -.->|imports| ext_typing
    estorides_core_ops_observability_py -.->|imports| ext___future__
    estorides_core_ops_observability_py -.->|imports| ext_json
    estorides_core_ops_observability_py -.->|imports| ext_re
    estorides_core_ops_observability_py -.->|imports| ext_threading
    estorides_core_ops_observability_py -.->|imports| ext_time
    estorides_core_ops_observability_py -.->|imports| ext_dataclasses
    estorides_core_ops_observability_py -.->|imports| ext_typing
    estorides_core_ops_observability_py -.->|imports| ext_config
    estorides_core_orchestrator_py -.->|imports| ext___future__
    estorides_core_orchestrator_py -.->|imports| ext_asyncio
    estorides_core_orchestrator_py -.->|imports| ext_json
    estorides_core_orchestrator_py -.->|imports| ext_logging
    estorides_core_orchestrator_py -.->|imports| ext_os
    estorides_core_orchestrator_py -.->|imports| ext_re
    estorides_core_orchestrator_py -.->|imports| ext_time
    estorides_core_orchestrator_py -.->|imports| ext_pathlib
    estorides_core_orchestrator_py -.->|imports| ext_typing
    ext_estorides_llm["estorides_llm"]
    class ext_estorides_llm ext;
    estorides_core_orchestrator_py -.->|imports| ext_estorides_llm
    ext_async_client["async_client"]
    class ext_async_client ext;
    estorides_core_orchestrator_py -.->|imports| ext_async_client
    estorides_core_orchestrator_py -.->|imports| ext_config
    estorides_core_orchestrator_py -.->|imports| ext_entity_extraction
    ext_event_bus["event_bus"]
    class ext_event_bus ext;
    estorides_core_orchestrator_py -.->|imports| ext_event_bus
    ext_knowledge_graph["knowledge_graph"]
    class ext_knowledge_graph ext;
    estorides_core_orchestrator_py -.->|imports| ext_knowledge_graph
    ext_mitre_attack["mitre_attack"]
    class ext_mitre_attack ext;
    estorides_core_orchestrator_py -.->|imports| ext_mitre_attack
    estorides_core_orchestrator_py -.->|imports| ext_ontology
    ext_parsers["parsers"]
    class ext_parsers ext;
    estorides_core_orchestrator_py -.->|imports| ext_parsers
    ext_recon_fusion["recon_fusion"]
    class ext_recon_fusion ext;
    estorides_core_orchestrator_py -.->|imports| ext_recon_fusion
    ext_relationship_inference["relationship_inference"]
    class ext_relationship_inference ext;
    estorides_core_orchestrator_py -.->|imports| ext_relationship_inference
    ext_source_loader["source_loader"]
    class ext_source_loader ext;
    estorides_core_orchestrator_py -.->|imports| ext_source_loader
    ext_system_app_sources["system_app_sources"]
    class ext_system_app_sources ext;
    estorides_core_orchestrator_py -.->|imports| ext_system_app_sources
    ext_graph_kuzu["graph_kuzu"]
    class ext_graph_kuzu ext;
    estorides_core_orchestrator_py -.->|imports| ext_graph_kuzu
    estorides_core_orchestrator_py -.->|imports| ext_cases
    ext_intel_resolver["intel_resolver"]
    class ext_intel_resolver ext;
    estorides_core_orchestrator_py -.->|imports| ext_intel_resolver
    estorides_core_orchestrator_py -.->|imports| ext_entity_resolution
    ext_fusion_store["fusion_store"]
    class ext_fusion_store ext;
    estorides_core_orchestrator_py -.->|imports| ext_fusion_store
    estorides_core_orchestrator_py -.->|imports| ext_fusion_store
    ext_entity_store["entity_store"]
    class ext_entity_store ext;
    estorides_core_orchestrator_py -.->|imports| ext_entity_store
    estorides_core_orchestrator_py -.->|imports| ext_system_app_sources
    ext_pagination["pagination"]
    class ext_pagination ext;
    estorides_core_orchestrator_py -.->|imports| ext_pagination
    estorides_core_orchestrator_py -.->|imports| ext_pagination
    estorides_core_osiris_sources_py -.->|imports| ext___future__
    estorides_core_osiris_sources_py -.->|imports| ext_csv
    estorides_core_osiris_sources_py -.->|imports| ext_io
    estorides_core_osiris_sources_py -.->|imports| ext_json
    estorides_core_osiris_sources_py -.->|imports| ext_logging
    estorides_core_osiris_sources_py -.->|imports| ext_re
    estorides_core_osiris_sources_py -.->|imports| ext_time
    estorides_core_osiris_sources_py -.->|imports| ext_pathlib
    estorides_core_osiris_sources_py -.->|imports| ext_typing
    estorides_core_osiris_sources_py -.->|imports| ext_requests
    estorides_core_osiris_sources_py -.->|imports| ext_config
    estorides_core_osiris_sources_py -.->|imports| ext_ssrf_guard
    estorides_core_pagination_py -.->|imports| ext___future__
    estorides_core_pagination_py -.->|imports| ext_dataclasses
    estorides_core_pagination_py -.->|imports| ext_typing
    estorides_core_parsers_py -.->|imports| ext___future__
    estorides_core_parsers_py -.->|imports| ext_json
    estorides_core_parsers_py -.->|imports| ext_logging
    estorides_core_parsers_py -.->|imports| ext_re
    estorides_core_parsers_py -.->|imports| ext_collections_abc
    estorides_core_parsers_py -.->|imports| ext_typing
    estorides_core_pdns_monitor_py -.->|imports| ext___future__
    estorides_core_pdns_monitor_py -.->|imports| ext_logging
    estorides_core_pdns_monitor_py -.->|imports| ext_dataclasses
    estorides_core_pdns_monitor_py -.->|imports| ext_typing
    estorides_core_people_intel_py -.->|imports| ext___future__
    estorides_core_people_intel_py -.->|imports| ext_logging
    estorides_core_people_intel_py -.->|imports| ext_re
    estorides_core_people_intel_py -.->|imports| ext_dataclasses
    estorides_core_people_intel_py -.->|imports| ext_typing
    estorides_core_pivot_engine_py -.->|imports| ext___future__
    ext_heapq["heapq"]
    class ext_heapq ext;
    estorides_core_pivot_engine_py -.->|imports| ext_heapq
    ext_itertools["itertools"]
    class ext_itertools ext;
    estorides_core_pivot_engine_py -.->|imports| ext_itertools
    estorides_core_pivot_engine_py -.->|imports| ext_logging
    estorides_core_pivot_engine_py -.->|imports| ext_time
    estorides_core_pivot_engine_py -.->|imports| ext_dataclasses
    estorides_core_pivot_engine_py -.->|imports| ext_typing
    estorides_core_pivot_engine_py -.->|imports| ext_config
    estorides_core_recon_fusion_py -.->|imports| ext___future__
    estorides_core_recon_fusion_py -.->|imports| ext_math
    estorides_core_recon_fusion_py -.->|imports| ext_time
    estorides_core_recon_fusion_py -.->|imports| ext_dataclasses
    ext_enum["enum"]
    class ext_enum ext;
    estorides_core_recon_fusion_py -.->|imports| ext_enum
    estorides_core_recon_fusion_py -.->|imports| ext_typing
    estorides_core_recon_fusion_py -.->|imports| ext_config
    estorides_core_recon_fusion_py -.->|imports| ext_config
    estorides_core_recon_fusion_py -.->|imports| ext_ids
    estorides_core_recon_fusion_py -.->|imports| ext_reliability_scoring
    estorides_core_recon_pipeline_py -.->|imports| ext___future__
    estorides_core_recon_pipeline_py -.->|imports| ext_logging
    ext_tech_fingerprint["tech_fingerprint"]
    class ext_tech_fingerprint ext;
    estorides_core_recon_pipeline_py -.->|imports| ext_tech_fingerprint
    ext_vuln_correlation["vuln_correlation"]
    class ext_vuln_correlation ext;
    estorides_core_recon_pipeline_py -.->|imports| ext_vuln_correlation
    ext_cloud_asset_discovery["cloud_asset_discovery"]
    class ext_cloud_asset_discovery ext;
    estorides_core_recon_pipeline_py -.->|imports| ext_cloud_asset_discovery
    ext_people_intel["people_intel"]
    class ext_people_intel ext;
    estorides_core_recon_pipeline_py -.->|imports| ext_people_intel
    ext_code_exposure["code_exposure"]
    class ext_code_exposure ext;
    estorides_core_recon_pipeline_py -.->|imports| ext_code_exposure
    ext_supply_chain["supply_chain"]
    class ext_supply_chain ext;
    estorides_core_recon_pipeline_py -.->|imports| ext_supply_chain
    ext_pdns_monitor["pdns_monitor"]
    class ext_pdns_monitor ext;
    estorides_core_recon_pipeline_py -.->|imports| ext_pdns_monitor
    ext_target_scoring["target_scoring"]
    class ext_target_scoring ext;
    estorides_core_recon_pipeline_py -.->|imports| ext_target_scoring
    estorides_core_relationship_inference_py -.->|imports| ext___future__
    estorides_core_relationship_inference_py -.->|imports| ext_logging
    estorides_core_relationship_inference_py -.->|imports| ext_typing
    estorides_core_reliability_scoring_py -.->|imports| ext___future__
    estorides_core_reliability_scoring_py -.->|imports| ext_math
    estorides_core_reliability_scoring_py -.->|imports| ext_collections_abc
    estorides_core_reliability_scoring_py -.->|imports| ext_dataclasses
    estorides_core_reliability_scoring_py -.->|imports| ext_enum
    estorides_core_scope_py -.->|imports| ext___future__
    estorides_core_scope_py -.->|imports| ext_ipaddress
    estorides_core_scope_py -.->|imports| ext_json
    estorides_core_scope_py -.->|imports| ext_logging
    estorides_core_scope_py -.->|imports| ext_re
    estorides_core_scope_py -.->|imports| ext_abc
    estorides_core_scope_py -.->|imports| ext_dataclasses
    estorides_core_scope_py -.->|imports| ext_pathlib
    estorides_core_scope_py -.->|imports| ext_typing
    estorides_core_search_telemetry_py -.->|imports| ext___future__
    estorides_core_search_telemetry_py -.->|imports| ext_re
    estorides_core_search_telemetry_py -.->|imports| ext_dataclasses
    estorides_core_search_telemetry_py -.->|imports| ext_typing
    estorides_core_search_telemetry_py -.->|imports| ext_config
    estorides_core_socmint_py -.->|imports| ext___future__
    estorides_core_socmint_py -.->|imports| ext_logging
    estorides_core_socmint_py -.->|imports| ext_re
    estorides_core_socmint_py -.->|imports| ext_time
    estorides_core_socmint_py -.->|imports| ext_dataclasses
    estorides_core_socmint_py -.->|imports| ext_typing
    estorides_core_source_health_monitoring_py -.->|imports| ext___future__
    estorides_core_source_health_monitoring_py -.->|imports| ext_collections_abc
    estorides_core_source_health_monitoring_py -.->|imports| ext_dataclasses
    estorides_core_source_health_monitoring_py -.->|imports| ext_enum
    estorides_core_source_health_monitoring_py -.->|imports| ext_envutil
    estorides_core_source_health_monitoring_py -.->|imports| ext_envutil
    estorides_core_source_loader_py -.->|imports| ext___future__
    estorides_core_source_loader_py -.->|imports| ext_logging
    estorides_core_source_loader_py -.->|imports| ext_re
    estorides_core_source_loader_py -.->|imports| ext_collections_abc
    estorides_core_source_loader_py -.->|imports| ext_pathlib
    estorides_core_source_loader_py -.->|imports| ext_typing
    ext_yaml["yaml"]
    class ext_yaml ext;
    estorides_core_source_loader_py -.->|imports| ext_yaml
    estorides_core_source_loader_py -.->|imports| ext_config
    estorides_core_sqlite_store_py -.->|imports| ext___future__
    estorides_core_sqlite_store_py -.->|imports| ext_sqlite3
    estorides_core_sqlite_store_py -.->|imports| ext_threading
    estorides_core_sqlite_store_py -.->|imports| ext_collections_abc
    estorides_core_sqlite_store_py -.->|imports| ext_contextlib
    estorides_core_sqlite_store_py -.->|imports| ext_dataclasses
    estorides_core_sqlite_store_py -.->|imports| ext_pathlib
    estorides_core_sqlite_store_py -.->|imports| ext_typing
    estorides_core_ssrf_guard_py -.->|imports| ext___future__
    estorides_core_ssrf_guard_py -.->|imports| ext_ipaddress
    estorides_core_ssrf_guard_py -.->|imports| ext_logging
    estorides_core_ssrf_guard_py -.->|imports| ext_os
    ext_socket["socket"]
    class ext_socket ext;
    estorides_core_ssrf_guard_py -.->|imports| ext_socket
    estorides_core_ssrf_guard_py -.->|imports| ext_dataclasses
    estorides_core_ssrf_guard_py -.->|imports| ext_typing
    estorides_core_ssrf_guard_py -.->|imports| ext_urllib_parse
    estorides_core_supply_chain_py -.->|imports| ext___future__
    estorides_core_supply_chain_py -.->|imports| ext_logging
    estorides_core_supply_chain_py -.->|imports| ext_re
    estorides_core_supply_chain_py -.->|imports| ext_dataclasses
    estorides_core_supply_chain_py -.->|imports| ext_typing
    estorides_core_system_app_sources_py -.->|imports| ext___future__
    estorides_core_system_app_sources_py -.->|imports| ext_json
    estorides_core_system_app_sources_py -.->|imports| ext_logging
    ext_platform["platform"]
    class ext_platform ext;
    estorides_core_system_app_sources_py -.->|imports| ext_platform
    estorides_core_system_app_sources_py -.->|imports| ext_re
    estorides_core_system_app_sources_py -.->|imports| ext_shutil
    ext_tempfile["tempfile"]
    class ext_tempfile ext;
    estorides_core_system_app_sources_py -.->|imports| ext_tempfile
    estorides_core_system_app_sources_py -.->|imports| ext_time
    estorides_core_system_app_sources_py -.->|imports| ext_collections_abc
    estorides_core_system_app_sources_py -.->|imports| ext_dataclasses
    estorides_core_system_app_sources_py -.->|imports| ext_pathlib
    estorides_core_system_app_sources_py -.->|imports| ext_typing
    estorides_core_system_app_sources_py -.->|imports| ext_config
    estorides_core_system_app_sources_py -.->|imports| ext_parsers
    estorides_core_system_app_sources_py -.->|imports| ext_tool_runner
    estorides_core_system_app_sources_py -.->|imports| ext_tool_runner
    estorides_core_tech_fingerprint_py -.->|imports| ext___future__
    estorides_core_tech_fingerprint_py -.->|imports| ext_logging
    estorides_core_tech_fingerprint_py -.->|imports| ext_re
    estorides_core_tech_fingerprint_py -.->|imports| ext_dataclasses
    estorides_core_tech_fingerprint_py -.->|imports| ext_typing
    estorides_core_tool_install_py -.->|imports| ext___future__
    estorides_core_tool_install_py -.->|imports| ext_logging
    estorides_core_tool_install_py -.->|imports| ext_os
    estorides_core_tool_install_py -.->|imports| ext_re
    ext_shlex["shlex"]
    class ext_shlex ext;
    estorides_core_tool_install_py -.->|imports| ext_shlex
    estorides_core_tool_install_py -.->|imports| ext_shutil
    estorides_core_tool_install_py -.->|imports| ext_subprocess
    estorides_core_tool_install_py -.->|imports| ext_time
    estorides_core_tool_install_py -.->|imports| ext_dataclasses
    estorides_core_tool_install_py -.->|imports| ext_pathlib
    estorides_core_tool_install_py -.->|imports| ext_typing
    estorides_core_tool_install_py -.->|imports| ext_yaml
    estorides_core_tool_install_py -.->|imports| ext_config
    estorides_core_tool_install_py -.->|imports| ext_tool_runner
    estorides_core_tool_install_py -.->|imports| ext_config
    estorides_core_tool_install_py -.->|imports| ext_argparse
    estorides_core_tool_runner_py -.->|imports| ext___future__
    estorides_core_tool_runner_py -.->|imports| ext_hashlib
    estorides_core_tool_runner_py -.->|imports| ext_logging
    estorides_core_tool_runner_py -.->|imports| ext_shutil
    estorides_core_tool_runner_py -.->|imports| ext_subprocess
    estorides_core_tool_runner_py -.->|imports| ext_dataclasses
    estorides_core_tool_runner_py -.->|imports| ext_typing
    estorides_core_tool_runner_py -.->|imports| ext_config
    estorides_core_tool_runner_py -.->|imports| ext_entity_extraction
    estorides_core_transforms_py -.->|imports| ext___future__
    estorides_core_transforms_py -.->|imports| ext_logging
    estorides_core_transforms_py -.->|imports| ext_re
    estorides_core_transforms_py -.->|imports| ext_collections_abc
    estorides_core_transforms_py -.->|imports| ext_dataclasses
    estorides_core_transforms_py -.->|imports| ext_pathlib
    estorides_core_transforms_py -.->|imports| ext_typing
    estorides_core_transforms_py -.->|imports| ext_config
    estorides_core_transforms_py -.->|imports| ext_intel_resolver
    estorides_core_transforms_py -.->|imports| ext_re
    estorides_core_transforms_py -.->|imports| ext_yaml
    estorides_core_transliteration_py -.->|imports| ext___future__
    ext_unicodedata["unicodedata"]
    class ext_unicodedata ext;
    estorides_core_transliteration_py -.->|imports| ext_unicodedata
    estorides_core_transliteration_py -.->|imports| ext_typing
    estorides_core_validation_py -.->|imports| ext___future__
    estorides_core_validation_py -.->|imports| ext_re
    estorides_core_validation_py -.->|imports| ext_unicodedata
    estorides_core_validation_py -.->|imports| ext_dataclasses
    estorides_core_validation_py -.->|imports| ext_typing
    estorides_core_validation_py -.->|imports| ext_entity_extraction
    estorides_core_vuln_correlation_py -.->|imports| ext___future__
    estorides_core_vuln_correlation_py -.->|imports| ext_logging
    estorides_core_vuln_correlation_py -.->|imports| ext_re
    estorides_core_vuln_correlation_py -.->|imports| ext_dataclasses
    estorides_core_vuln_correlation_py -.->|imports| ext_typing
    estorides_core_web_security_py -.->|imports| ext___future__
    ext_hmac["hmac"]
    class ext_hmac ext;
    estorides_core_web_security_py -.->|imports| ext_hmac
    estorides_core_web_security_py -.->|imports| ext_logging
    estorides_core_web_security_py -.->|imports| ext_os
    ext_secrets["secrets"]
    class ext_secrets ext;
    estorides_core_web_security_py -.->|imports| ext_secrets
    estorides_core_web_security_py -.->|imports| ext_collections_abc
    estorides_core_web_security_py -.->|imports| ext_dataclasses
    ext_functools["functools"]
    class ext_functools ext;
    estorides_core_web_security_py -.->|imports| ext_functools
    estorides_core_web_security_py -.->|imports| ext_typing
    estorides_core_web_security_py -.->|imports| ext_urllib_parse
    ext_flask["flask"]
    class ext_flask ext;
    estorides_core_web_security_py -.->|imports| ext_flask
    estorides_core_web_security_py -.->|imports| ext_envutil
    estorides_core_web_security_py -.->|imports| ext_envutil
    estorides_core_web_security_py -.->|imports| ext_flask
    ext_stix["stix"]
    class ext_stix ext;
    estorides_export___init___py -.->|imports| ext_stix
    ext_misp["misp"]
    class ext_misp ext;
    estorides_export___init___py -.->|imports| ext_misp
    ext_encryption["encryption"]
    class ext_encryption ext;
    estorides_export___init___py -.->|imports| ext_encryption
    ext_report["report"]
    class ext_report ext;
    estorides_export___init___py -.->|imports| ext_report
    ext_recon_report["recon_report"]
    class ext_recon_report ext;
    estorides_export___init___py -.->|imports| ext_recon_report
    estorides_export_encryption_py -.->|imports| ext___future__
    estorides_export_encryption_py -.->|imports| ext_logging
    estorides_export_encryption_py -.->|imports| ext_shutil
    estorides_export_encryption_py -.->|imports| ext_subprocess
    estorides_export_encryption_py -.->|imports| ext_pathlib
    estorides_export_encryption_py -.->|imports| ext_typing
    estorides_export_encryption_py -.->|imports| ext_estorides_core_knowledge_graph
    estorides_export_encryption_py -.->|imports| ext_estorides_export
    estorides_export_encryption_py -.->|imports| ext_estorides_export
    estorides_export_misp_py -.->|imports| ext___future__
    estorides_export_misp_py -.->|imports| ext_json
    estorides_export_misp_py -.->|imports| ext_logging
    estorides_export_misp_py -.->|imports| ext_time
    estorides_export_misp_py -.->|imports| ext_uuid
    estorides_export_misp_py -.->|imports| ext_pathlib
    estorides_export_misp_py -.->|imports| ext_typing
    estorides_export_misp_py -.->|imports| ext_estorides_core_config
    estorides_export_misp_py -.->|imports| ext_estorides_core_knowledge_graph
    estorides_export_recon_report_py -.->|imports| ext___future__
    estorides_export_recon_report_py -.->|imports| ext_logging
    estorides_export_recon_report_py -.->|imports| ext_re
    estorides_export_recon_report_py -.->|imports| ext_dataclasses
    estorides_export_recon_report_py -.->|imports| ext_datetime
    estorides_export_recon_report_py -.->|imports| ext_typing
    estorides_export_report_py -.->|imports| ext___future__
    estorides_export_report_py -.->|imports| ext_datetime
    estorides_export_report_py -.->|imports| ext_logging
    estorides_export_report_py -.->|imports| ext_collections
    estorides_export_report_py -.->|imports| ext_typing
    estorides_export_stix_py -.->|imports| ext___future__
    estorides_export_stix_py -.->|imports| ext_json
    estorides_export_stix_py -.->|imports| ext_logging
    estorides_export_stix_py -.->|imports| ext_time
    estorides_export_stix_py -.->|imports| ext_uuid
    estorides_export_stix_py -.->|imports| ext_pathlib
    estorides_export_stix_py -.->|imports| ext_typing
    estorides_export_stix_py -.->|imports| ext_estorides_core_config
    estorides_export_stix_py -.->|imports| ext_estorides_core_knowledge_graph
    ext_manager["manager"]
    class ext_manager ext;
    estorides_llm___init___py -.->|imports| ext_manager
    estorides_llm_intelligence_prompts_py -.->|imports| ext___future__
    estorides_llm_intelligence_prompts_py -.->|imports| ext_json
    estorides_llm_intelligence_prompts_py -.->|imports| ext_typing
    estorides_llm_manager_py -.->|imports| ext___future__
    estorides_llm_manager_py -.->|imports| ext_json
    estorides_llm_manager_py -.->|imports| ext_logging
    estorides_llm_manager_py -.->|imports| ext_os
    estorides_llm_manager_py -.->|imports| ext_collections_abc
    estorides_llm_manager_py -.->|imports| ext_typing
    estorides_llm_manager_py -.->|imports| ext_requests
    estorides_llm_manager_py -.->|imports| ext_estorides_core_config
    ext_estorides_llm_intelligence_prompts["estorides_llm.intelligence_prompts"]
    class ext_estorides_llm_intelligence_prompts ext;
    estorides_llm_manager_py -.->|imports| ext_estorides_llm_intelligence_prompts
    estorides_web_py -.->|imports| ext___future__
    estorides_web_py -.->|imports| ext_asyncio
    estorides_web_py -.->|imports| ext_json
    estorides_web_py -.->|imports| ext_logging
    estorides_web_py -.->|imports| ext_os
    ext_queue["queue"]
    class ext_queue ext;
    estorides_web_py -.->|imports| ext_queue
    estorides_web_py -.->|imports| ext_shutil
    estorides_web_py -.->|imports| ext_sys
    estorides_web_py -.->|imports| ext_tempfile
    estorides_web_py -.->|imports| ext_threading
    estorides_web_py -.->|imports| ext_time
    estorides_web_py -.->|imports| ext_collections_abc
    estorides_web_py -.->|imports| ext_functools
    estorides_web_py -.->|imports| ext_pathlib
    estorides_web_py -.->|imports| ext_typing
    estorides_web_py -.->|imports| ext_flask
    ext_estorides_core_audit["estorides_core.audit"]
    class ext_estorides_core_audit ext;
    estorides_web_py -.->|imports| ext_estorides_core_audit
    estorides_web_py -.->|imports| ext_estorides_core_config
    estorides_web_py -.->|imports| ext_estorides_core_discoverer
    estorides_web_py -.->|imports| ext_estorides_core_discoverer
    estorides_web_py -.->|imports| ext_estorides_core_entity_extraction
    ext_estorides_core_feeds["estorides_core.feeds"]
    class ext_estorides_core_feeds ext;
    estorides_web_py -.->|imports| ext_estorides_core_feeds
    ext_estorides_core_job_registry["estorides_core.job_registry"]
    class ext_estorides_core_job_registry ext;
    estorides_web_py -.->|imports| ext_estorides_core_job_registry
    estorides_web_py -.->|imports| ext_estorides_core_knowledge_graph
    ext_estorides_core_lan_web["estorides_core.lan_web"]
    class ext_estorides_core_lan_web ext;
    estorides_web_py -.->|imports| ext_estorides_core_lan_web
    estorides_web_py -.->|imports| ext_estorides_core_lan_web
    ext_estorides_core_openapi["estorides_core.openapi"]
    class ext_estorides_core_openapi ext;
    estorides_web_py -.->|imports| ext_estorides_core_openapi
    ext_estorides_core_ops_observability["estorides_core.ops_observability"]
    class ext_estorides_core_ops_observability ext;
    estorides_web_py -.->|imports| ext_estorides_core_ops_observability
    estorides_web_py -.->|imports| ext_estorides_core_orchestrator
    ext_estorides_core_pivot_engine["estorides_core.pivot_engine"]
    class ext_estorides_core_pivot_engine ext;
    estorides_web_py -.->|imports| ext_estorides_core_pivot_engine
    ext_estorides_core_search_telemetry["estorides_core.search_telemetry"]
    class ext_estorides_core_search_telemetry ext;
    estorides_web_py -.->|imports| ext_estorides_core_search_telemetry
    estorides_web_py -.->|imports| ext_estorides_core_validation
    ext_estorides_core_web_security["estorides_core.web_security"]
    class ext_estorides_core_web_security ext;
    estorides_web_py -.->|imports| ext_estorides_core_web_security
    estorides_web_py -.->|imports| ext_estorides_export
    ext_estorides_export_encryption["estorides_export.encryption"]
    class ext_estorides_export_encryption ext;
    estorides_web_py -.->|imports| ext_estorides_export_encryption
    ext_estorides_web_tools["estorides_web_tools"]
    class ext_estorides_web_tools ext;
    estorides_web_py -.->|imports| ext_estorides_web_tools
    estorides_web_py -.->|imports| ext_networkx
    ext_estorides_core_graph_rag_search["estorides_core.graph_rag_search"]
    class ext_estorides_core_graph_rag_search ext;
    estorides_web_py -.->|imports| ext_estorides_core_graph_rag_search
    estorides_web_py -.->|imports| ext_estorides_core_knowledge_graph
    ext_estorides_core["estorides_core"]
    class ext_estorides_core ext;
    estorides_web_py -.->|imports| ext_estorides_core
    estorides_web_py -.->|imports| ext_estorides_core_lan_web
    estorides_web_py -.->|imports| ext_estorides_core
    estorides_web_py -.->|imports| ext_estorides_core_entity_extraction
    estorides_web_py -.->|imports| ext_networkx
    estorides_web_py -.->|imports| ext_networkx
    estorides_web_py -.->|imports| ext_estorides_core_cases
    estorides_web_py -.->|imports| ext_estorides_core_graph_kuzu
    ext_estorides_core_intel_resolver["estorides_core.intel_resolver"]
    class ext_estorides_core_intel_resolver ext;
    estorides_web_py -.->|imports| ext_estorides_core_intel_resolver
    estorides_web_py -.->|imports| ext_estorides_core_fusion_store
    ext_estorides_core_socmint["estorides_core.socmint"]
    class ext_estorides_core_socmint ext;
    estorides_web_py -.->|imports| ext_estorides_core_socmint
    estorides_web_py -.->|imports| ext_estorides_core_monitoring
    estorides_web_py -.->|imports| ext_estorides_core_monitoring
    estorides_web_py -.->|imports| ext_estorides_core_monitoring
    estorides_web_py -.->|imports| ext_estorides_core_alerter
    estorides_web_py -.->|imports| ext_estorides_core_monitoring
    ext_estorides_core_transforms["estorides_core.transforms"]
    class ext_estorides_core_transforms ext;
    estorides_web_py -.->|imports| ext_estorides_core_transforms
    estorides_web_py -.->|imports| ext_estorides_core_transforms
    estorides_web_py -.->|imports| ext_estorides_core
    estorides_web_py -.->|imports| ext_estorides_core_graph_force
    estorides_web_py -.->|imports| ext_estorides_core
    ext_estorides_core_fusion_analytics["estorides_core.fusion_analytics"]
    class ext_estorides_core_fusion_analytics ext;
    estorides_web_py -.->|imports| ext_estorides_core_fusion_analytics
    estorides_web_py -.->|imports| ext_estorides_core_entity_extraction
    estorides_web_py -.->|imports| ext_estorides_core_graph_kuzu
    estorides_web_py -.->|imports| ext_estorides_core_orchestrator
    estorides_web_tools_py -.->|imports| ext___future__
    estorides_web_tools_py -.->|imports| ext_logging
    estorides_web_tools_py -.->|imports| ext_threading
    estorides_web_tools_py -.->|imports| ext_typing
    estorides_web_tools_py -.->|imports| ext_flask
    estorides_web_tools_py -.->|imports| ext_estorides_core_audit
    ext_estorides_core_tool_install["estorides_core.tool_install"]
    class ext_estorides_core_tool_install ext;
    estorides_web_tools_py -.->|imports| ext_estorides_core_tool_install
    estorides_web_tools_py -.->|imports| ext_estorides_core_web_security
    estorides_web_tools_py -.->|imports| ext_estorides_web
    ext_function["function"]
    class ext_function ext;
    static_js_estorides_js -.->|imports| ext_function
    ext_querySelector["querySelector"]
    class ext_querySelector ext;
    static_js_estorides_js -.->|imports| ext_querySelector
    ext_from["from"]
    class ext_from ext;
    static_js_estorides_js -.->|imports| ext_from
    ext_setVisible["setVisible"]
    class ext_setVisible ext;
    static_js_estorides_js -.->|imports| ext_setVisible
    ext_removeProperty["removeProperty"]
    class ext_removeProperty ext;
    static_js_estorides_js -.->|imports| ext_removeProperty
    ext_isLanQueryLocal["isLanQueryLocal"]
    class ext_isLanQueryLocal ext;
    static_js_estorides_js -.->|imports| ext_isLanQueryLocal
    ext_trim["trim"]
    class ext_trim ext;
    static_js_estorides_js -.->|imports| ext_trim
    ext_test["test"]
    class ext_test ext;
    static_js_estorides_js -.->|imports| ext_test
    static_js_estorides_js -.->|imports| ext_test
    static_js_estorides_js -.->|imports| ext_test
    static_js_estorides_js -.->|imports| ext_test
    static_js_estorides_js -.->|imports| ext_test
    ext_lanRunFields["lanRunFields"]
    class ext_lanRunFields ext;
    static_js_estorides_js -.->|imports| ext_lanRunFields
    ext_ensureLanConsent["ensureLanConsent"]
    class ext_ensureLanConsent ext;
    static_js_estorides_js -.->|imports| ext_ensureLanConsent
    ext_resolve["resolve"]
    class ext_resolve ext;
    static_js_estorides_js -.->|imports| ext_resolve
    static_js_estorides_js -.->|imports| ext_resolve
    static_js_estorides_js -.->|imports| ext_resolve
    ext_purifyHTML["purifyHTML"]
    class ext_purifyHTML ext;
    static_js_estorides_js -.->|imports| ext_purifyHTML
    ext_setSanitizedHTML["setSanitizedHTML"]
    class ext_setSanitizedHTML ext;
    static_js_estorides_js -.->|imports| ext_setSanitizedHTML
    static_js_estorides_js -.->|imports| ext_setVisible
    static_js_estorides_js -.->|imports| ext_setVisible
    ext_removeEventListener["removeEventListener"]
    class ext_removeEventListener ext;
    static_js_estorides_js -.->|imports| ext_removeEventListener
    static_js_estorides_js -.->|imports| ext_removeEventListener
    static_js_estorides_js -.->|imports| ext_resolve
    ext_done["done"]
    class ext_done ext;
    static_js_estorides_js -.->|imports| ext_done
    static_js_estorides_js -.->|imports| ext_done
    ext_addEventListener["addEventListener"]
    class ext_addEventListener ext;
    static_js_estorides_js -.->|imports| ext_addEventListener
    static_js_estorides_js -.->|imports| ext_addEventListener
    ext_installAuthFetch["installAuthFetch"]
    class ext_installAuthFetch ext;
    static_js_estorides_js -.->|imports| ext_installAuthFetch
    static_js_estorides_js -.->|imports| ext_querySelector
    ext_getAttribute["getAttribute"]
    class ext_getAttribute ext;
    static_js_estorides_js -.->|imports| ext_getAttribute
    static_js_estorides_js -.->|imports| ext_trim
    ext_bind["bind"]
    class ext_bind ext;
    static_js_estorides_js -.->|imports| ext_bind
    static_js_estorides_js -.->|imports| ext_function
    ext_set["set"]
    class ext_set ext;
    static_js_estorides_js -.->|imports| ext_set
    static_js_estorides_js -.->|imports| ext_set
    ext_origFetch["origFetch"]
    class ext_origFetch ext;
    static_js_estorides_js -.->|imports| ext_origFetch
    ext_readTelemetry["readTelemetry"]
    class ext_readTelemetry ext;
    static_js_estorides_js -.->|imports| ext_readTelemetry
    ext_getElementById["getElementById"]
    class ext_getElementById ext;
    static_js_estorides_js -.->|imports| ext_getElementById
    ext_parse["parse"]
    class ext_parse ext;
    static_js_estorides_js -.->|imports| ext_parse
    ext_assign["assign"]
    class ext_assign ext;
    static_js_estorides_js -.->|imports| ext_assign
    ext_helpers["helpers"]
    class ext_helpers ext;
    static_js_estorides_js -.->|imports| ext_helpers
    ext_detectQueryTypeLocal["detectQueryTypeLocal"]
    class ext_detectQueryTypeLocal ext;
    static_js_estorides_js -.->|imports| ext_detectQueryTypeLocal
    static_js_estorides_js -.->|imports| ext_trim
    static_js_estorides_js -.->|imports| ext_test
    static_js_estorides_js -.->|imports| ext_test
    static_js_estorides_js -.->|imports| ext_test
    ext_showToast["showToast"]
    class ext_showToast ext;
    static_js_estorides_js -.->|imports| ext_showToast
    static_js_estorides_js -.->|imports| ext_getElementById
    ext_createElement["createElement"]
    class ext_createElement ext;
    static_js_estorides_js -.->|imports| ext_createElement
    ext_escapeHTML["escapeHTML"]
    class ext_escapeHTML ext;
    static_js_estorides_js -.->|imports| ext_escapeHTML
    static_js_estorides_js -.->|imports| ext_escapeHTML
    ext_appendChild["appendChild"]
    class ext_appendChild ext;
    static_js_estorides_js -.->|imports| ext_appendChild
    ext_setTimeout["setTimeout"]
    class ext_setTimeout ext;
    static_js_estorides_js -.->|imports| ext_setTimeout
    static_js_estorides_js -.->|imports| ext_setTimeout
    ext_remove["remove"]
    class ext_remove ext;
    static_js_estorides_js -.->|imports| ext_remove
    ext_updateQueryChip["updateQueryChip"]
    class ext_updateQueryChip ext;
    static_js_estorides_js -.->|imports| ext_updateQueryChip
    static_js_estorides_js -.->|imports| ext_getElementById
    static_js_estorides_js -.->|imports| ext_remove
    ext_add["add"]
    class ext_add ext;
    static_js_estorides_js -.->|imports| ext_add
    ext_setRunProgress["setRunProgress"]
    class ext_setRunProgress ext;
    static_js_estorides_js -.->|imports| ext_setRunProgress
    static_js_estorides_js -.->|imports| ext_getElementById
    static_js_estorides_js -.->|imports| ext_getElementById
    static_js_estorides_js -.->|imports| ext_getElementById
    static_js_estorides_js -.->|imports| ext_setVisible
    ext_min["min"]
    class ext_min ext;
    static_js_estorides_js -.->|imports| ext_min
    ext_setAttribute["setAttribute"]
    class ext_setAttribute ext;
    static_js_estorides_js -.->|imports| ext_setAttribute
    static_js_estorides_js -.->|imports| ext_setAttribute
    static_js_estorides_js -.->|imports| ext_setAttribute
    static_js_estorides_js -.->|imports| ext_setVisible
    static_js_estorides_js -.->|imports| ext_setAttribute
    ext_removeAttribute["removeAttribute"]
    class ext_removeAttribute ext;
    static_js_estorides_js -.->|imports| ext_removeAttribute
    ext_showEmptyState["showEmptyState"]
    class ext_showEmptyState ext;
    static_js_estorides_js -.->|imports| ext_showEmptyState
    static_js_estorides_js -.->|imports| ext_getElementById
    static_js_estorides_js -.->|imports| ext_getElementById
    static_js_estorides_js -.->|imports| ext_setVisible
    static_js_estorides_js -.->|imports| ext_setVisible
    ext_summariseObservation["summariseObservation"]
    class ext_summariseObservation ext;
    static_js_estorides_js -.->|imports| ext_summariseObservation
    ext_truncate["truncate"]
    class ext_truncate ext;
    static_js_estorides_js -.->|imports| ext_truncate
    ext_error["error"]
    class ext_error ext;
    static_js_estorides_js -.->|imports| ext_error
    ext_item["item"]
    class ext_item ext;
    static_js_estorides_js -.->|imports| ext_item
    ext_slice["slice"]
    class ext_slice ext;
    static_js_estorides_js -.->|imports| ext_slice
    ext_forEach["forEach"]
    class ext_forEach ext;
    static_js_estorides_js -.->|imports| ext_forEach
    ext_push["push"]
    class ext_push ext;
    static_js_estorides_js -.->|imports| ext_push
    static_js_estorides_js -.->|imports| ext_truncate
    ext_keys["keys"]
    class ext_keys ext;
    static_js_estorides_js -.->|imports| ext_keys
    static_js_estorides_js -.->|imports| ext_slice
    ext_map["map"]
    class ext_map ext;
    static_js_estorides_js -.->|imports| ext_map
    ext_isArray["isArray"]
    class ext_isArray ext;
    static_js_estorides_js -.->|imports| ext_isArray
    static_js_estorides_js -.->|imports| ext_truncate
    static_js_estorides_js -.->|imports| ext_push
    ext_buildResultCard["buildResultCard"]
    class ext_buildResultCard ext;
    static_js_estorides_js -.->|imports| ext_buildResultCard
    static_js_estorides_js -.->|imports| ext_summariseObservation
    static_js_estorides_js -.->|imports| ext_createElement
    static_js_estorides_js -.->|imports| ext_setAttribute
    static_js_estorides_js -.->|imports| ext_setAttribute
    static_js_estorides_js -.->|imports| ext_setAttribute
    ext_random["random"]
    class ext_random ext;
    static_js_estorides_js -.->|imports| ext_random
    ext_toString["toString"]
    class ext_toString ext;
    static_js_estorides_js -.->|imports| ext_toString
    static_js_estorides_js -.->|imports| ext_slice
    static_js_estorides_js -.->|imports| ext_escapeHTML
    static_js_estorides_js -.->|imports| ext_escapeHTML
    static_js_estorides_js -.->|imports| ext_escapeHTML
    static_js_estorides_js -.->|imports| ext_escapeHTML
    static_js_estorides_js -.->|imports| ext_escapeHTML
    static_js_estorides_js -.->|imports| ext_map
    static_js_estorides_js -.->|imports| ext_escapeHTML
    ext_join["join"]
    class ext_join ext;
    static_js_estorides_js -.->|imports| ext_join
    static_js_estorides_js -.->|imports| ext_escapeHTML
    static_js_estorides_js -.->|imports| ext_escapeHTML
    ext_attempt["attempt"]
    class ext_attempt ext;
    static_js_estorides_js -.->|imports| ext_attempt
    static_js_estorides_js -.->|imports| ext_querySelector
    static_js_estorides_js -.->|imports| ext_addEventListener
    ext_toggle["toggle"]
    class ext_toggle ext;
    static_js_estorides_js -.->|imports| ext_toggle
    static_js_estorides_js -.->|imports| ext_querySelector
    static_js_estorides_js -.->|imports| ext_addEventListener
    ext_stopPropagation["stopPropagation"]
    class ext_stopPropagation ext;
    static_js_estorides_js -.->|imports| ext_stopPropagation
    static_js_estorides_js -.->|imports| ext_toggle
    ext_contains["contains"]
    class ext_contains ext;
    static_js_estorides_js -.->|imports| ext_contains
    static_js_estorides_js -.->|imports| ext_querySelector
    static_js_estorides_js -.->|imports| ext_addEventListener
    static_js_estorides_js -.->|imports| ext_stopPropagation
    ext_requestToolInstall["requestToolInstall"]
    class ext_requestToolInstall ext;
    static_js_estorides_js -.->|imports| ext_requestToolInstall
    static_js_estorides_js -.->|imports| ext_requestToolInstall
    ext_fetch["fetch"]
    class ext_fetch ext;
    static_js_estorides_js -.->|imports| ext_fetch
    ext_stringify["stringify"]
    class ext_stringify ext;
    static_js_estorides_js -.->|imports| ext_stringify
    ext_then["then"]
    class ext_then ext;
    static_js_estorides_js -.->|imports| ext_then
    static_js_estorides_js -.->|imports| ext_showToast
    static_js_estorides_js -.->|imports| ext_json
    static_js_estorides_js -.->|imports| ext_then
    ext_pollToolInstall["pollToolInstall"]
    class ext_pollToolInstall ext;
    static_js_estorides_js -.->|imports| ext_pollToolInstall
    static_js_estorides_js -.->|imports| ext_showToast
    static_js_estorides_js -.->|imports| ext_pollToolInstall
    static_js_estorides_js -.->|imports| ext_fetch
    static_js_estorides_js -.->|imports| ext_then
    static_js_estorides_js -.->|imports| ext_json
    static_js_estorides_js -.->|imports| ext_then
    static_js_estorides_js -.->|imports| ext_setTimeout
    static_js_estorides_js -.->|imports| ext_pollToolInstall
    static_js_estorides_js -.->|imports| ext_showToast
    ext__installErrMsg["_installErrMsg"]
    class ext__installErrMsg ext;
    static_js_estorides_js -.->|imports| ext__installErrMsg
    static_js_estorides_js -.->|imports| ext_showToast
    static_js_estorides_js -.->|imports| ext_showToast
    static_js_estorides_js -.->|imports| ext__installErrMsg
    static_js_estorides_js -.->|imports| ext_toString
    ext_replace["replace"]
    class ext_replace ext;
    static_js_estorides_js -.->|imports| ext_replace
    static_js_estorides_js -.->|imports| ext_trim
    static_js_estorides_js -.->|imports| ext_slice
    ext_message["message"]
    class ext_message ext;
    static_js_estorides_js -.->|imports| ext_message
    ext_populateCategoryFilter["populateCategoryFilter"]
    class ext_populateCategoryFilter ext;
    static_js_estorides_js -.->|imports| ext_populateCategoryFilter
    static_js_estorides_js -.->|imports| ext_getElementById
    static_js_estorides_js -.->|imports| ext_map
    static_js_estorides_js -.->|imports| ext_forEach
    static_js_estorides_js -.->|imports| ext_createElement
    static_js_estorides_js -.->|imports| ext_appendChild
    static_js_estorides_js -.->|imports| ext_add
    ext_applyResultFilters["applyResultFilters"]
    class ext_applyResultFilters ext;
    static_js_estorides_js -.->|imports| ext_applyResultFilters
    static_js_estorides_js -.->|imports| ext_getElementById
    static_js_estorides_js -.->|imports| ext_getElementById
    ext_toLowerCase["toLowerCase"]
    class ext_toLowerCase ext;
    static_js_estorides_js -.->|imports| ext_toLowerCase
    static_js_estorides_js -.->|imports| ext_getElementById
    static_js_estorides_js -.->|imports| ext_getElementById
    static_js_estorides_js -.->|imports| ext_getElementById
    static_js_estorides_js -.->|imports| ext_getElementById
    ext_querySelectorAll["querySelectorAll"]
    class ext_querySelectorAll ext;
    static_js_estorides_js -.->|imports| ext_querySelectorAll
    static_js_estorides_js -.->|imports| ext_forEach
    static_js_estorides_js -.->|imports| ext_toLowerCase
    ext_includes["includes"]
    class ext_includes ext;
    static_js_estorides_js -.->|imports| ext_includes
    static_js_estorides_js -.->|imports| ext_getAttribute
    static_js_estorides_js -.->|imports| ext_getAttribute
    static_js_estorides_js -.->|imports| ext_getElementById
    ext_bindResultFilters["bindResultFilters"]
    class ext_bindResultFilters ext;
    static_js_estorides_js -.->|imports| ext_bindResultFilters
    static_js_estorides_js -.->|imports| ext_getElementById
    static_js_estorides_js -.->|imports| ext_getElementById
    static_js_estorides_js -.->|imports| ext_getElementById
    static_js_estorides_js -.->|imports| ext_addEventListener
    static_js_estorides_js -.->|imports| ext_addEventListener
    static_js_estorides_js -.->|imports| ext_addEventListener
    ext_showFriendlyError["showFriendlyError"]
    class ext_showFriendlyError ext;
    static_js_estorides_js -.->|imports| ext_showFriendlyError
    static_js_estorides_js -.->|imports| ext_getElementById
    static_js_estorides_js -.->|imports| ext_createElement
    static_js_estorides_js -.->|imports| ext_escapeHTML
    static_js_estorides_js -.->|imports| ext_createElement
    static_js_estorides_js -.->|imports| ext_addEventListener
    static_js_estorides_js -.->|imports| ext_remove
    ext_retryFn["retryFn"]
    class ext_retryFn ext;
    static_js_estorides_js -.->|imports| ext_retryFn
    static_js_estorides_js -.->|imports| ext_appendChild
    static_js_estorides_js -.->|imports| ext_appendChild
    ext_focusGraphNodeByValue["focusGraphNodeByValue"]
    class ext_focusGraphNodeByValue ext;
    static_js_estorides_js -.->|imports| ext_focusGraphNodeByValue
    ext_find["find"]
    class ext_find ext;
    static_js_estorides_js -.->|imports| ext_find
    ext_focusNode["focusNode"]
    class ext_focusNode ext;
    static_js_estorides_js -.->|imports| ext_focusNode
    ext_selectNode["selectNode"]
    class ext_selectNode ext;
    static_js_estorides_js -.->|imports| ext_selectNode
    ext_switchSidebarTab["switchSidebarTab"]
    class ext_switchSidebarTab ext;
    static_js_estorides_js -.->|imports| ext_switchSidebarTab
    ext_click["click"]
    class ext_click ext;
    static_js_estorides_js -.->|imports| ext_click
    ext_switchCanvasTab["switchCanvasTab"]
    class ext_switchCanvasTab ext;
    static_js_estorides_js -.->|imports| ext_switchCanvasTab
    static_js_estorides_js -.->|imports| ext_click
    static_js_estorides_js -.->|imports| ext_map
    ext_setView["setView"]
    class ext_setView ext;
    static_js_estorides_js -.->|imports| ext_setView
    ext_instead["instead"]
    class ext_instead ext;
    static_js_estorides_js -.->|imports| ext_instead
    ext_tileLayer["tileLayer"]
    class ext_tileLayer ext;
    static_js_estorides_js -.->|imports| ext_tileLayer
    ext_addTo["addTo"]
    class ext_addTo ext;
    static_js_estorides_js -.->|imports| ext_addTo
    static_js_estorides_js -.->|imports| ext_tileLayer
    static_js_estorides_js -.->|imports| ext_addTo
    ext_clearMap["clearMap"]
    class ext_clearMap ext;
    static_js_estorides_js -.->|imports| ext_clearMap
    static_js_estorides_js -.->|imports| ext_forEach
    ext_removeLayer["removeLayer"]
    class ext_removeLayer ext;
    static_js_estorides_js -.->|imports| ext_removeLayer
    ext_plotPoints["plotPoints"]
    class ext_plotPoints ext;
    static_js_estorides_js -.->|imports| ext_plotPoints
    static_js_estorides_js -.->|imports| ext_clearMap
    static_js_estorides_js -.->|imports| ext_forEach
    ext_circleMarker["circleMarker"]
    class ext_circleMarker ext;
    static_js_estorides_js -.->|imports| ext_circleMarker
    ext_bindPopup["bindPopup"]
    class ext_bindPopup ext;
    static_js_estorides_js -.->|imports| ext_bindPopup
    static_js_estorides_js -.->|imports| ext_escapeHTML
    static_js_estorides_js -.->|imports| ext_escapeHTML
    static_js_estorides_js -.->|imports| ext_escapeHTML
    ext_escapeAttr["escapeAttr"]
    class ext_escapeAttr ext;
    static_js_estorides_js -.->|imports| ext_escapeAttr
    static_js_estorides_js -.->|imports| ext_escapeAttr
    static_js_estorides_js -.->|imports| ext_addTo
    static_js_estorides_js -.->|imports| ext_push
    static_js_estorides_js -.->|imports| ext_push
    static_js_estorides_js -.->|imports| ext_setView
    ext_fitBounds["fitBounds"]
    class ext_fitBounds ext;
    static_js_estorides_js -.->|imports| ext_fitBounds
    ext_buttons["buttons"]
    class ext_buttons ext;
    static_js_estorides_js -.->|imports| ext_buttons
    static_js_estorides_js -.->|imports| ext_querySelectorAll
    static_js_estorides_js -.->|imports| ext_forEach
    static_js_estorides_js -.->|imports| ext_addEventListener
    ext_preventDefault["preventDefault"]
    class ext_preventDefault ext;
    static_js_estorides_js -.->|imports| ext_preventDefault
    static_js_estorides_js -.->|imports| ext_getAttribute
    static_js_estorides_js -.->|imports| ext_getAttribute
    ext_expandNode["expandNode"]
    class ext_expandNode ext;
    static_js_estorides_js -.->|imports| ext_expandNode
    static_js_estorides_js -.->|imports| ext_forEach
    static_js_estorides_js -.->|imports| ext_addEventListener
    static_js_estorides_js -.->|imports| ext_forEach
    static_js_estorides_js -.->|imports| ext_remove
    static_js_estorides_js -.->|imports| ext_forEach
    static_js_estorides_js -.->|imports| ext_remove
    static_js_estorides_js -.->|imports| ext_add
    static_js_estorides_js -.->|imports| ext_add
    static_js_estorides_js -.->|imports| ext_forEach
    static_js_estorides_js -.->|imports| ext_addEventListener
    static_js_estorides_js -.->|imports| ext_forEach
    static_js_estorides_js -.->|imports| ext_remove
    static_js_estorides_js -.->|imports| ext_forEach
    static_js_estorides_js -.->|imports| ext_remove
    static_js_estorides_js -.->|imports| ext_add
    static_js_estorides_js -.->|imports| ext_add
    ext_invalidateSize["invalidateSize"]
    class ext_invalidateSize ext;
    static_js_estorides_js -.->|imports| ext_invalidateSize
    ext_drawGraph["drawGraph"]
    class ext_drawGraph ext;
    static_js_estorides_js -.->|imports| ext_drawGraph
    static_js_estorides_js -.->|imports| ext_addEventListener
    static_js_estorides_js -.->|imports| ext_addEventListener
    static_js_estorides_js -.->|imports| ext_addEventListener
    ext_runQuery["runQuery"]
    class ext_runQuery ext;
    static_js_estorides_js -.->|imports| ext_runQuery
    static_js_estorides_js -.->|imports| ext_addEventListener
    static_js_estorides_js -.->|imports| ext_updateQueryChip
    static_js_estorides_js -.->|imports| ext_addEventListener
    ext_clearAll["clearAll"]
    class ext_clearAll ext;
    static_js_estorides_js -.->|imports| ext_clearAll
    static_js_estorides_js -.->|imports| ext_addEventListener
    static_js_estorides_js -.->|imports| ext_addEventListener
    ext_replotStreamData["replotStreamData"]
    class ext_replotStreamData ext;
    static_js_estorides_js -.->|imports| ext_replotStreamData
    ext_renderTimeline["renderTimeline"]
    class ext_renderTimeline ext;
    static_js_estorides_js -.->|imports| ext_renderTimeline
    ext_now["now"]
    class ext_now ext;
    static_js_estorides_js -.->|imports| ext_now
    static_js_estorides_js -.->|imports| ext_plotPoints
    static_js_estorides_js -.->|imports| ext_renderTimeline
    ext_stopRunStream["stopRunStream"]
    class ext_stopRunStream ext;
    static_js_estorides_js -.->|imports| ext_stopRunStream
    static_js_estorides_js -.->|imports| ext_fetch
    ext_close["close"]
    class ext_close ext;
    static_js_estorides_js -.->|imports| ext_close
    ext_scope["scope"]
    class ext_scope ext;
    static_js_estorides_js -.->|imports| ext_scope
    ext_renderLanResults["renderLanResults"]
    class ext_renderLanResults ext;
    static_js_estorides_js -.->|imports| ext_renderLanResults
    static_js_estorides_js -.->|imports| ext_showEmptyState
    ext_device["device"]
    class ext_device ext;
    static_js_estorides_js -.->|imports| ext_device
    static_js_estorides_js -.->|imports| ext_forEach
    static_js_estorides_js -.->|imports| ext_createElement
    static_js_estorides_js -.->|imports| ext_createElement
    static_js_estorides_js -.->|imports| ext_createElement
    static_js_estorides_js -.->|imports| ext_appendChild
    static_js_estorides_js -.->|imports| ext_createElement
    static_js_estorides_js -.->|imports| ext_appendChild
    static_js_estorides_js -.->|imports| ext_appendChild
    static_js_estorides_js -.->|imports| ext_createElement
    static_js_estorides_js -.->|imports| ext_push
    static_js_estorides_js -.->|imports| ext_push
    static_js_estorides_js -.->|imports| ext_push
    static_js_estorides_js -.->|imports| ext_createElement
    static_js_estorides_js -.->|imports| ext_forEach
    static_js_estorides_js -.->|imports| ext_createElement
    static_js_estorides_js -.->|imports| ext_appendChild
    static_js_estorides_js -.->|imports| ext_appendChild
    static_js_estorides_js -.->|imports| ext_appendChild
    static_js_estorides_js -.->|imports| ext_appendChild
    static_js_estorides_js -.->|imports| ext_createElement
    static_js_estorides_js -.->|imports| ext_appendChild
    static_js_estorides_js -.->|imports| ext_keys
    static_js_estorides_js -.->|imports| ext_createElement
    static_js_estorides_js -.->|imports| ext_createElement
    static_js_estorides_js -.->|imports| ext_createElement
    ext_bundle["bundle"]
    class ext_bundle ext;
    static_js_estorides_js -.->|imports| ext_bundle
    static_js_estorides_js -.->|imports| ext_appendChild
    static_js_estorides_js -.->|imports| ext_appendChild
    static_js_estorides_js -.->|imports| ext_createElement
    static_js_estorides_js -.->|imports| ext_createElement
    static_js_estorides_js -.->|imports| ext_forEach
    static_js_estorides_js -.->|imports| ext_createElement
    static_js_estorides_js -.->|imports| ext_appendChild
    static_js_estorides_js -.->|imports| ext_appendChild
    static_js_estorides_js -.->|imports| ext_appendChild
    static_js_estorides_js -.->|imports| ext_appendChild
    ext_startLanScan["startLanScan"]
    class ext_startLanScan ext;
    static_js_estorides_js -.->|imports| ext_startLanScan
    static_js_estorides_js -.->|imports| ext_trim
    static_js_estorides_js -.->|imports| ext_fetch
    static_js_estorides_js -.->|imports| ext_json
    ext_setStatus["setStatus"]
    class ext_setStatus ext;
    static_js_estorides_js -.->|imports| ext_setStatus
    static_js_estorides_js -.->|imports| ext_showFriendlyError
    static_js_estorides_js -.->|imports| ext_ensureLanConsent
    static_js_estorides_js -.->|imports| ext_setStatus
    static_js_estorides_js -.->|imports| ext_showToast
    static_js_estorides_js -.->|imports| ext_setStatus
    ext_showWorkingIndicator["showWorkingIndicator"]
    class ext_showWorkingIndicator ext;
    static_js_estorides_js -.->|imports| ext_showWorkingIndicator
    static_js_estorides_js -.->|imports| ext_fetch
    static_js_estorides_js -.->|imports| ext_json
    static_js_estorides_js -.->|imports| ext_setStatus
    ext_setStatusDot["setStatusDot"]
    class ext_setStatusDot ext;
    static_js_estorides_js -.->|imports| ext_setStatusDot
    static_js_estorides_js -.->|imports| ext_showFriendlyError
    static_js_estorides_js -.->|imports| ext_renderLanResults
    static_js_estorides_js -.->|imports| ext_showToast
    static_js_estorides_js -.->|imports| ext_showToast
    static_js_estorides_js -.->|imports| ext_setStatus
    static_js_estorides_js -.->|imports| ext_setStatusDot
    static_js_estorides_js -.->|imports| ext_drawGraph
    static_js_estorides_js -.->|imports| ext_setStatusDot
    static_js_estorides_js -.->|imports| ext_setStatus
    static_js_estorides_js -.->|imports| ext_showFriendlyError
    ext_hideWorkingIndicator["hideWorkingIndicator"]
    class ext_hideWorkingIndicator ext;
    static_js_estorides_js -.->|imports| ext_hideWorkingIndicator
    static_js_estorides_js -.->|imports| ext_runQuery
    static_js_estorides_js -.->|imports| ext_trim
    static_js_estorides_js -.->|imports| ext_ensureLanConsent
    static_js_estorides_js -.->|imports| ext_setStatus
    static_js_estorides_js -.->|imports| ext_showToast
    static_js_estorides_js -.->|imports| ext_updateQueryChip
    static_js_estorides_js -.->|imports| ext_showEmptyState
    static_js_estorides_js -.->|imports| ext_setRunProgress
    static_js_estorides_js -.->|imports| ext_showWorkingIndicator
    static_js_estorides_js -.->|imports| ext_bindResultFilters
    static_js_estorides_js -.->|imports| ext_stopRunStream
    static_js_estorides_js -.->|imports| ext_setStatus
    static_js_estorides_js -.->|imports| ext_now
    static_js_estorides_js -.->|imports| ext_clearMap
    static_js_estorides_js -.->|imports| ext_fetch
    static_js_estorides_js -.->|imports| ext_json
    static_js_estorides_js -.->|imports| ext_setStatus
    static_js_estorides_js -.->|imports| ext_setStatusDot
    static_js_estorides_js -.->|imports| ext_showFriendlyError
    static_js_estorides_js -.->|imports| ext_setRunProgress
    static_js_estorides_js -.->|imports| ext_hideWorkingIndicator
    static_js_estorides_js -.->|imports| ext_setStatus
    ext_runQueryBlocking["runQueryBlocking"]
    class ext_runQueryBlocking ext;
    static_js_estorides_js -.->|imports| ext_runQueryBlocking
    static_js_estorides_js -.->|imports| ext_setStatus
    static_js_estorides_js -.->|imports| ext_addEventListener
    static_js_estorides_js -.->|imports| ext_parse
    ext_handleRunStreamEvent["handleRunStreamEvent"]
    class ext_handleRunStreamEvent ext;
    static_js_estorides_js -.->|imports| ext_handleRunStreamEvent
    static_js_estorides_js -.->|imports| ext_addEventListener
    ext_round["round"]
    class ext_round ext;
    static_js_estorides_js -.->|imports| ext_round
    ext_floor["floor"]
    class ext_floor ext;
    static_js_estorides_js -.->|imports| ext_floor
    static_js_estorides_js -.->|imports| ext_setStatusDot
    static_js_estorides_js -.->|imports| ext_setStatus
    static_js_estorides_js -.->|imports| ext_setRunProgress
    static_js_estorides_js -.->|imports| ext_showToast
    static_js_estorides_js -.->|imports| ext_stopRunStream
    static_js_estorides_js -.->|imports| ext_hideWorkingIndicator
    static_js_estorides_js -.->|imports| ext_setTimeout
    static_js_estorides_js -.->|imports| ext_function
    static_js_estorides_js -.->|imports| ext_setStatusDot
    static_js_estorides_js -.->|imports| ext_runQueryBlocking
    static_js_estorides_js -.->|imports| ext_showWorkingIndicator
    static_js_estorides_js -.->|imports| ext_fetch
    static_js_estorides_js -.->|imports| ext_json
    static_js_estorides_js -.->|imports| ext_setStatus
    static_js_estorides_js -.->|imports| ext_showFriendlyError
    static_js_estorides_js -.->|imports| ext_runQueryBlocking
    static_js_estorides_js -.->|imports| ext_hideWorkingIndicator
    ext_renderResult["renderResult"]
    class ext_renderResult ext;
    static_js_estorides_js -.->|imports| ext_renderResult
    ext_renderTieredResults["renderTieredResults"]
    class ext_renderTieredResults ext;
    static_js_estorides_js -.->|imports| ext_renderTieredResults
    static_js_estorides_js -.->|imports| ext_setStatusDot
    static_js_estorides_js -.->|imports| ext_setStatus
    static_js_estorides_js -.->|imports| ext_showToast
    static_js_estorides_js -.->|imports| ext_setStatusDot
    static_js_estorides_js -.->|imports| ext_setStatus
    static_js_estorides_js -.->|imports| ext_showFriendlyError
    static_js_estorides_js -.->|imports| ext_runQueryBlocking
    static_js_estorides_js -.->|imports| ext_setRunProgress
    static_js_estorides_js -.->|imports| ext_hideWorkingIndicator
    ext_searchEntity["searchEntity"]
    class ext_searchEntity ext;
    static_js_estorides_js -.->|imports| ext_searchEntity
    static_js_estorides_js -.->|imports| ext_ensureLanConsent
    static_js_estorides_js -.->|imports| ext_setStatus
    static_js_estorides_js -.->|imports| ext_showToast
    static_js_estorides_js -.->|imports| ext_hideWorkingIndicator
    static_js_estorides_js -.->|imports| ext_stopRunStream
    static_js_estorides_js -.->|imports| ext_showWorkingIndicator
    static_js_estorides_js -.->|imports| ext_setStatus
    static_js_estorides_js -.->|imports| ext_trim
    static_js_estorides_js -.->|imports| ext_fetch
    static_js_estorides_js -.->|imports| ext_json
    static_js_estorides_js -.->|imports| ext_setStatusDot
    static_js_estorides_js -.->|imports| ext_setStatus
    static_js_estorides_js -.->|imports| ext_hideWorkingIndicator
    static_js_estorides_js -.->|imports| ext_setStatus
    static_js_estorides_js -.->|imports| ext_addEventListener
    static_js_estorides_js -.->|imports| ext_parse
    static_js_estorides_js -.->|imports| ext_handleRunStreamEvent
    static_js_estorides_js -.->|imports| ext_addEventListener
    static_js_estorides_js -.->|imports| ext_setStatusDot
    static_js_estorides_js -.->|imports| ext_setStatus
    static_js_estorides_js -.->|imports| ext_showToast
    static_js_estorides_js -.->|imports| ext_stopRunStream
    static_js_estorides_js -.->|imports| ext_hideWorkingIndicator
    static_js_estorides_js -.->|imports| ext_function
    static_js_estorides_js -.->|imports| ext_setStatusDot
    static_js_estorides_js -.->|imports| ext_handleRunStreamEvent
    static_js_estorides_js -.->|imports| ext_setStatusDot
    static_js_estorides_js -.->|imports| ext_setStatus
    static_js_estorides_js -.->|imports| ext_setStatusDot
    static_js_estorides_js -.->|imports| ext_setStatus
    ext_appendStreamObservation["appendStreamObservation"]
    class ext_appendStreamObservation ext;
    static_js_estorides_js -.->|imports| ext_appendStreamObservation
    ext_appendStreamEntity["appendStreamEntity"]
    class ext_appendStreamEntity ext;
    static_js_estorides_js -.->|imports| ext_appendStreamEntity
    ext_loadAnalysisModels["loadAnalysisModels"]
    class ext_loadAnalysisModels ext;
    static_js_estorides_js -.->|imports| ext_loadAnalysisModels
    ext_renderAnalysis["renderAnalysis"]
    class ext_renderAnalysis ext;
    static_js_estorides_js -.->|imports| ext_renderAnalysis
    ext_renderGraphSummary["renderGraphSummary"]
    class ext_renderGraphSummary ext;
    static_js_estorides_js -.->|imports| ext_renderGraphSummary
    static_js_estorides_js -.->|imports| ext_setStatus
    static_js_estorides_js -.->|imports| ext_setStatusDot
    static_js_estorides_js -.->|imports| ext_setStatus
    static_js_estorides_js -.->|imports| ext_appendStreamObservation
    static_js_estorides_js -.->|imports| ext_add
    static_js_estorides_js -.->|imports| ext_push
    static_js_estorides_js -.->|imports| ext_buildResultCard
    static_js_estorides_js -.->|imports| ext_appendChild
    static_js_estorides_js -.->|imports| ext_populateCategoryFilter
    static_js_estorides_js -.->|imports| ext_applyResultFilters
    static_js_estorides_js -.->|imports| ext_setRunProgress
    static_js_estorides_js -.->|imports| ext_showToast
    ext_sources["sources"]
    class ext_sources ext;
    static_js_estorides_js -.->|imports| ext_sources
    static_js_estorides_js -.->|imports| ext_replotStreamData
    static_js_estorides_js -.->|imports| ext_appendStreamEntity
    static_js_estorides_js -.->|imports| ext_add
    static_js_estorides_js -.->|imports| ext_push
    static_js_estorides_js -.->|imports| ext_createElement
    static_js_estorides_js -.->|imports| ext_setAttribute
    static_js_estorides_js -.->|imports| ext_setAttribute
    static_js_estorides_js -.->|imports| ext_setAttribute
    static_js_estorides_js -.->|imports| ext_escapeHTML
    static_js_estorides_js -.->|imports| ext_escapeHTML
    static_js_estorides_js -.->|imports| ext_escapeHTML
    static_js_estorides_js -.->|imports| ext_addEventListener
    ext_dispatchEvent["dispatchEvent"]
    class ext_dispatchEvent ext;
    static_js_estorides_js -.->|imports| ext_dispatchEvent
    static_js_estorides_js -.->|imports| ext_appendChild
    static_js_estorides_js -.->|imports| ext_clearAll
    static_js_estorides_js -.->|imports| ext_stopRunStream
    static_js_estorides_js -.->|imports| ext_clearMap
    static_js_estorides_js -.->|imports| ext_remove
    ext_stop["stop"]
    class ext_stop ext;
    static_js_estorides_js -.->|imports| ext_stop
    static_js_estorides_js -.->|imports| ext_updateQueryChip
    static_js_estorides_js -.->|imports| ext_setRunProgress
    static_js_estorides_js -.->|imports| ext_showEmptyState
    static_js_estorides_js -.->|imports| ext_setStatusDot
    static_js_estorides_js -.->|imports| ext_setStatus
    static_js_estorides_js -.->|imports| ext_setStatus
    static_js_estorides_js -.->|imports| ext_renderResult
    static_js_estorides_js -.->|imports| ext_showEmptyState
    static_js_estorides_js -.->|imports| ext_bindResultFilters
    static_js_estorides_js -.->|imports| ext_slice
    static_js_estorides_js -.->|imports| ext_forEach
    static_js_estorides_js -.->|imports| ext_appendChild
    static_js_estorides_js -.->|imports| ext_push
    static_js_estorides_js -.->|imports| ext_populateCategoryFilter
    static_js_estorides_js -.->|imports| ext_applyResultFilters
    ext_renderEntities["renderEntities"]
    class ext_renderEntities ext;
    static_js_estorides_js -.->|imports| ext_renderEntities
    static_js_estorides_js -.->|imports| ext_loadAnalysisModels
    static_js_estorides_js -.->|imports| ext_renderAnalysis
    static_js_estorides_js -.->|imports| ext_renderGraphSummary
    static_js_estorides_js -.->|imports| ext_plotPoints
    static_js_estorides_js -.->|imports| ext_renderTimeline
    ext_run["run"]
    class ext_run ext;
    static_js_estorides_js -.->|imports| ext_run
    static_js_estorides_js -.->|imports| ext_loadAnalysisModels
    static_js_estorides_js -.->|imports| ext_fetch
    static_js_estorides_js -.->|imports| ext_then
    static_js_estorides_js -.->|imports| ext_json
    static_js_estorides_js -.->|imports| ext_then
    static_js_estorides_js -.->|imports| ext_isArray
    ext_renderAnalysisModels["renderAnalysisModels"]
    class ext_renderAnalysisModels ext;
    static_js_estorides_js -.->|imports| ext_renderAnalysisModels
    static_js_estorides_js -.->|imports| ext_renderAnalysisModels
    ext_makeModelPill["makeModelPill"]
    class ext_makeModelPill ext;
    static_js_estorides_js -.->|imports| ext_makeModelPill
    static_js_estorides_js -.->|imports| ext_appendChild
    static_js_estorides_js -.->|imports| ext_forEach
    static_js_estorides_js -.->|imports| ext_appendChild
    static_js_estorides_js -.->|imports| ext_makeModelPill
    static_js_estorides_js -.->|imports| ext_createElement
    static_js_estorides_js -.->|imports| ext_addEventListener
    ext_reanalyze["reanalyze"]
    class ext_reanalyze ext;
    static_js_estorides_js -.->|imports| ext_reanalyze
    static_js_estorides_js -.->|imports| ext_renderAnalysis
    static_js_estorides_js -.->|imports| ext_escapeHTML
    static_js_estorides_js -.->|imports| ext_escapeHTML
    ext_renderMarkdownInto["renderMarkdownInto"]
    class ext_renderMarkdownInto ext;
    static_js_estorides_js -.->|imports| ext_renderMarkdownInto
    ext_setThinkingVisible["setThinkingVisible"]
    class ext_setThinkingVisible ext;
    static_js_estorides_js -.->|imports| ext_setThinkingVisible
    ext_renderer["renderer"]
    class ext_renderer ext;
    static_js_estorides_js -.->|imports| ext_renderer
    static_js_estorides_js -.->|imports| ext_renderMarkdownInto
    static_js_estorides_js -.->|imports| ext_parse
    static_js_estorides_js -.->|imports| ext_setSanitizedHTML
    static_js_estorides_js -.->|imports| ext_setThinkingVisible
    ext_toggleThinking["toggleThinking"]
    class ext_toggleThinking ext;
    static_js_estorides_js -.->|imports| ext_toggleThinking
    static_js_estorides_js -.->|imports| ext_reanalyze
    ext_abort["abort"]
    class ext_abort ext;
    static_js_estorides_js -.->|imports| ext_abort
    ext_box["box"]
    class ext_box ext;
    static_js_estorides_js -.->|imports| ext_box
    static_js_estorides_js -.->|imports| ext_trim
    static_js_estorides_js -.->|imports| ext_slice
    static_js_estorides_js -.->|imports| ext_fetch
    static_js_estorides_js -.->|imports| ext_then
    ext_getReader["getReader"]
    class ext_getReader ext;
    static_js_estorides_js -.->|imports| ext_getReader
    ext_requestAnimationFrame["requestAnimationFrame"]
    class ext_requestAnimationFrame ext;
    static_js_estorides_js -.->|imports| ext_requestAnimationFrame
    static_js_estorides_js -.->|imports| ext_renderMarkdownInto
    ext_split["split"]
    class ext_split ext;
    static_js_estorides_js -.->|imports| ext_split
    ext_pop["pop"]
    class ext_pop ext;
    static_js_estorides_js -.->|imports| ext_pop
    static_js_estorides_js -.->|imports| ext_forEach
    static_js_estorides_js -.->|imports| ext_trim
    static_js_estorides_js -.->|imports| ext_parse
    ext_scheduleRender["scheduleRender"]
    class ext_scheduleRender ext;
    static_js_estorides_js -.->|imports| ext_scheduleRender
    static_js_estorides_js -.->|imports| ext_escapeHTML
    ext_read["read"]
    class ext_read ext;
    static_js_estorides_js -.->|imports| ext_read
    static_js_estorides_js -.->|imports| ext_then
    ext_flush["flush"]
    class ext_flush ext;
    static_js_estorides_js -.->|imports| ext_flush
    ext_decode["decode"]
    class ext_decode ext;
    static_js_estorides_js -.->|imports| ext_decode
    static_js_estorides_js -.->|imports| ext_flush
    ext_pump["pump"]
    class ext_pump ext;
    static_js_estorides_js -.->|imports| ext_pump
    static_js_estorides_js -.->|imports| ext_pump
    ext_finally["finally"]
    class ext_finally ext;
    static_js_estorides_js -.->|imports| ext_finally
    ext_data["data"]
    class ext_data ext;
    static_js_estorides_js -.->|imports| ext_data
    ext_analyseEntity["analyseEntity"]
    class ext_analyseEntity ext;
    static_js_estorides_js -.->|imports| ext_analyseEntity
    static_js_estorides_js -.->|imports| ext_push
    static_js_estorides_js -.->|imports| ext_switchSidebarTab
    static_js_estorides_js -.->|imports| ext_loadAnalysisModels
    static_js_estorides_js -.->|imports| ext_renderAnalysis
    static_js_estorides_js -.->|imports| ext_reanalyze
    ext_server["server"]
    class ext_server ext;
    static_js_estorides_js -.->|imports| ext_server
    static_js_estorides_js -.->|imports| ext_addEventListener
    static_js_estorides_js -.->|imports| ext_expandNode
    static_js_estorides_js -.->|imports| ext_expandNode
    static_js_estorides_js -.->|imports| ext_setStatus
    static_js_estorides_js -.->|imports| ext_fetch
    ext_encodeURIComponent["encodeURIComponent"]
    class ext_encodeURIComponent ext;
    static_js_estorides_js -.->|imports| ext_encodeURIComponent
    static_js_estorides_js -.->|imports| ext_json
    static_js_estorides_js -.->|imports| ext_setStatus
    static_js_estorides_js -.->|imports| ext_setStatus
    ext_mergeExpansionIntoGraph["mergeExpansionIntoGraph"]
    class ext_mergeExpansionIntoGraph ext;
    static_js_estorides_js -.->|imports| ext_mergeExpansionIntoGraph
    static_js_estorides_js -.->|imports| ext_setStatus
    static_js_estorides_js -.->|imports| ext_mergeExpansionIntoGraph
    static_js_estorides_js -.->|imports| ext_forEach
    static_js_estorides_js -.->|imports| ext_add
    static_js_estorides_js -.->|imports| ext_push
    static_js_estorides_js -.->|imports| ext_forEach
    static_js_estorides_js -.->|imports| ext_add
    static_js_estorides_js -.->|imports| ext_push
    static_js_estorides_js -.->|imports| ext_push
    ext_shift["shift"]
    class ext_shift ext;
    static_js_estorides_js -.->|imports| ext_shift
    ext_drawGraphWithExtras["drawGraphWithExtras"]
    class ext_drawGraphWithExtras ext;
    static_js_estorides_js -.->|imports| ext_drawGraphWithExtras
    static_js_estorides_js -.->|imports| ext_forEach
```

---

## UML Class Diagram

Auto-generated Mermaid class diagram from parsed class-level symbols. Shows classes, structs, interfaces, traits, and their methods with inheritance and dependency relationships.

```mermaid
classDiagram
  class active_recon_py_NmapResult {
    <<class>>
    +_parse_nmap_stdout(stdout, tool_name)
    +_parse_nikto_stdout(stdout, tool_name)
    +_parse_sqlmap_stdout(stdout, tool_name)
    +_parse_dnsrecon_stdout(stdout, tool_name)
    +_parse_harvester_stdout(stdout, tool_name)
    +run_nmap(target, args)
    +run_nikto(target, args)
    +run_sqlmap(target, args)
    +run_dnsrecon(target, args)
    +run_theHarvester(target, args)
  }
  class active_recon_py_NiktoResult {
    <<class>>
    +_parse_nmap_stdout(stdout, tool_name)
    +_parse_nikto_stdout(stdout, tool_name)
    +_parse_sqlmap_stdout(stdout, tool_name)
    +_parse_dnsrecon_stdout(stdout, tool_name)
    +_parse_harvester_stdout(stdout, tool_name)
    +run_nmap(target, args)
    +run_nikto(target, args)
    +run_sqlmap(target, args)
    +run_dnsrecon(target, args)
    +run_theHarvester(target, args)
  }
  class active_recon_py_SqlmapResult {
    <<class>>
    +_parse_nmap_stdout(stdout, tool_name)
    +_parse_nikto_stdout(stdout, tool_name)
    +_parse_sqlmap_stdout(stdout, tool_name)
    +_parse_dnsrecon_stdout(stdout, tool_name)
    +_parse_harvester_stdout(stdout, tool_name)
    +run_nmap(target, args)
    +run_nikto(target, args)
    +run_sqlmap(target, args)
    +run_dnsrecon(target, args)
    +run_theHarvester(target, args)
  }
  class active_recon_py_DnsreconResult {
    <<class>>
    +_parse_nmap_stdout(stdout, tool_name)
    +_parse_nikto_stdout(stdout, tool_name)
    +_parse_sqlmap_stdout(stdout, tool_name)
    +_parse_dnsrecon_stdout(stdout, tool_name)
    +_parse_harvester_stdout(stdout, tool_name)
    +run_nmap(target, args)
    +run_nikto(target, args)
    +run_sqlmap(target, args)
    +run_dnsrecon(target, args)
    +run_theHarvester(target, args)
  }
  class active_recon_py_TheHarvesterResult {
    <<class>>
    +_parse_nmap_stdout(stdout, tool_name)
    +_parse_nikto_stdout(stdout, tool_name)
    +_parse_sqlmap_stdout(stdout, tool_name)
    +_parse_dnsrecon_stdout(stdout, tool_name)
    +_parse_harvester_stdout(stdout, tool_name)
    +run_nmap(target, args)
    +run_nikto(target, args)
    +run_sqlmap(target, args)
    +run_dnsrecon(target, args)
    +run_theHarvester(target, args)
  }
  class alerter_py__NoRedirectHandler {
    <<class>>
    +_check_cooldown(channel)
    +_http_post(url, payload)
    +_send_slack(webhook_url, title, body, severity)
    +_send_discord(webhook_url, title, body, severity)
    +_send_telegram(bot_token, chat_id, title, body, severity)
    +_send_email(smtp_host, smtp_port, smtp_user, smtp_pass, from_addr, to_addr, title, body, severity)
    +_send_webhook(webhook_url, title, body, severity)
    +_fmt_time(ts)
    +redirect_request(self, req, fp, code, msg, headers, newurl)
    +send(self, channel, title, body, severity)
  }
  class alerter_py_AlertDispatcher {
    <<class>>
    +_check_cooldown(channel)
    +_http_post(url, payload)
    +_send_slack(webhook_url, title, body, severity)
    +_send_discord(webhook_url, title, body, severity)
    +_send_telegram(bot_token, chat_id, title, body, severity)
    +_send_email(smtp_host, smtp_port, smtp_user, smtp_pass, from_addr, to_addr, title, body, severity)
    +_send_webhook(webhook_url, title, body, severity)
    +_fmt_time(ts)
    +redirect_request(self, req, fp, code, msg, headers, newurl)
    +send(self, channel, title, body, severity)
  }
  class async_client_py_CircuitBreaker {
    <<class>>
    +_is_socks(proxy)
    +_redact_proxy(proxy)
    +sync_fetch(method, url)
    +allow(self, host)
    +record_success(self, host)
    +record_failure(self, host)
    +__init__(self, path)
    +_conn(self)
    +_init_db(self)
    +_key(method, url, body)
  }
  class async_client_py_ResponseCache {
    <<class>>
    +_is_socks(proxy)
    +_redact_proxy(proxy)
    +sync_fetch(method, url)
    +allow(self, host)
    +record_success(self, host)
    +record_failure(self, host)
    +__init__(self, path)
    +_conn(self)
    +_init_db(self)
    +_key(method, url, body)
  }
  class async_client_py_AsyncClient {
    <<class>>
    +_is_socks(proxy)
    +_redact_proxy(proxy)
    +sync_fetch(method, url)
    +allow(self, host)
    +record_success(self, host)
    +record_failure(self, host)
    +__init__(self, path)
    +_conn(self)
    +_init_db(self)
    +_key(method, url, body)
  }
  class audit_py_AuditEvent {
    <<class>>
    +to_jsonl(self)
    +__init__(self, path)
    +record(self, event)
    +_maybe_rotate_locked(self)
    +query(self, event)
    +__init__(self)
    +allow(self, key)
    +reset(self, key)
  }
  class audit_py_AuditLog {
    <<class>>
    +to_jsonl(self)
    +__init__(self, path)
    +record(self, event)
    +_maybe_rotate_locked(self)
    +query(self, event)
    +__init__(self)
    +allow(self, key)
    +reset(self, key)
  }
  class audit_py_RateLimiter {
    <<class>>
    +to_jsonl(self)
    +__init__(self, path)
    +record(self, event)
    +_maybe_rotate_locked(self)
    +query(self, event)
    +__init__(self)
    +allow(self, key)
    +reset(self, key)
  }
  class cases_py_CaseStore {
    <<class>>
    +_init_schema(self)
    +fts_available(self)
    +search_observations_fts(self, query, limit)
    +create_case(self, query, query_type, notes)
    +add_observation(self, case_id, observation)
    +add_entities(self, case_id, entities)
    +finalise(self, case_id, analysis, kg_path, mitre, source_count, obs_count, entity_count, status)
    +delete_case(self, case_id)
    +set_notes(self, case_id, notes)
    +get_case(self, case_id)
  }
  class change_detection_py_Edge {
    <<class>>
    +_truncate_key(key)
    +_change_id(kind, entity_id, diff_signature)
    +_reliability_floor(letter)
    +_filter_sources_by_reliability(sources, min_reliability)
    +_property_diff(before, after)
    +_edge_set(edges)
    +_union_sources(a, b)
    +_make_change(kind, entity_id, entity_type, entity_value, sig)
    +_below_min_reliability(source, min_reliability)
    +detect_changes(snapshot_before, snapshot_after)
  }
  class change_detection_py_SnapshotEntity {
    <<class>>
    +_truncate_key(key)
    +_change_id(kind, entity_id, diff_signature)
    +_reliability_floor(letter)
    +_filter_sources_by_reliability(sources, min_reliability)
    +_property_diff(before, after)
    +_edge_set(edges)
    +_union_sources(a, b)
    +_make_change(kind, entity_id, entity_type, entity_value, sig)
    +_below_min_reliability(source, min_reliability)
    +detect_changes(snapshot_before, snapshot_after)
  }
  class change_detection_py_Snapshot {
    <<class>>
    +_truncate_key(key)
    +_change_id(kind, entity_id, diff_signature)
    +_reliability_floor(letter)
    +_filter_sources_by_reliability(sources, min_reliability)
    +_property_diff(before, after)
    +_edge_set(edges)
    +_union_sources(a, b)
    +_make_change(kind, entity_id, entity_type, entity_value, sig)
    +_below_min_reliability(source, min_reliability)
    +detect_changes(snapshot_before, snapshot_after)
  }
  class change_detection_py_ChangeConfig {
    <<class>>
    +_truncate_key(key)
    +_change_id(kind, entity_id, diff_signature)
    +_reliability_floor(letter)
    +_filter_sources_by_reliability(sources, min_reliability)
    +_property_diff(before, after)
    +_edge_set(edges)
    +_union_sources(a, b)
    +_make_change(kind, entity_id, entity_type, entity_value, sig)
    +_below_min_reliability(source, min_reliability)
    +detect_changes(snapshot_before, snapshot_after)
  }
  class change_detection_py_Diff {
    <<class>>
    +_truncate_key(key)
    +_change_id(kind, entity_id, diff_signature)
    +_reliability_floor(letter)
    +_filter_sources_by_reliability(sources, min_reliability)
    +_property_diff(before, after)
    +_edge_set(edges)
    +_union_sources(a, b)
    +_make_change(kind, entity_id, entity_type, entity_value, sig)
    +_below_min_reliability(source, min_reliability)
    +detect_changes(snapshot_before, snapshot_after)
  }
  class change_detection_py_Change {
    <<class>>
    +_truncate_key(key)
    +_change_id(kind, entity_id, diff_signature)
    +_reliability_floor(letter)
    +_filter_sources_by_reliability(sources, min_reliability)
    +_property_diff(before, after)
    +_edge_set(edges)
    +_union_sources(a, b)
    +_make_change(kind, entity_id, entity_type, entity_value, sig)
    +_below_min_reliability(source, min_reliability)
    +detect_changes(snapshot_before, snapshot_after)
  }
  class change_detection_py_ChangeSummary {
    <<class>>
    +_truncate_key(key)
    +_change_id(kind, entity_id, diff_signature)
    +_reliability_floor(letter)
    +_filter_sources_by_reliability(sources, min_reliability)
    +_property_diff(before, after)
    +_edge_set(edges)
    +_union_sources(a, b)
    +_make_change(kind, entity_id, entity_type, entity_value, sig)
    +_below_min_reliability(source, min_reliability)
    +detect_changes(snapshot_before, snapshot_after)
  }
  class change_detection_py_ChangeReport {
    <<class>>
    +_truncate_key(key)
    +_change_id(kind, entity_id, diff_signature)
    +_reliability_floor(letter)
    +_filter_sources_by_reliability(sources, min_reliability)
    +_property_diff(before, after)
    +_edge_set(edges)
    +_union_sources(a, b)
    +_make_change(kind, entity_id, entity_type, entity_value, sig)
    +_below_min_reliability(source, min_reliability)
    +detect_changes(snapshot_before, snapshot_after)
  }
  class cloud_asset_discovery_py_CloudAsset {
    <<class>>
    +generate_bucket_names(domain)
    +assess_bucket(url, method)
    +to_dict(self)
    +to_dict(self)
  }
  class cloud_asset_discovery_py_CloudAssetDiscoveryResult {
    <<class>>
    +generate_bucket_names(domain)
    +assess_bucket(url, method)
    +to_dict(self)
    +to_dict(self)
  }
  class cloud_asset_discovery_py_CloudAssetDiscoveryError {
    <<class>>
    +generate_bucket_names(domain)
    +assess_bucket(url, method)
    +to_dict(self)
    +to_dict(self)
  }
  class code_exposure_py_CodeFinding {
    <<class>>
    +validate_aws_key(key)
    +_is_placeholder(text)
    +classify_finding(content, source, file_path)
    +analyse_findings(findings, rate_limited)
    +to_dict(self)
    +to_dict(self)
    +to_dict(self)
  }
  class code_exposure_py_SeveritySummary {
    <<class>>
    +validate_aws_key(key)
    +_is_placeholder(text)
    +classify_finding(content, source, file_path)
    +analyse_findings(findings, rate_limited)
    +to_dict(self)
    +to_dict(self)
    +to_dict(self)
  }
  class code_exposure_py_CodeExposureResult {
    <<class>>
    +validate_aws_key(key)
    +_is_placeholder(text)
    +classify_finding(content, source, file_path)
    +analyse_findings(findings, rate_limited)
    +to_dict(self)
    +to_dict(self)
    +to_dict(self)
  }
  class config_py_CacheConfig {
    <<class>>
    +_installed_share()
    +_resolve_dir(env_key, dirname)
    +_resolve_writable_dir(env_key, dirname)
    +ensure_data_dirs()
    +ensure_reports_dir()
    +_env_tool_allowlist()
    +contact_level(contact)
    +effective_proxies(explicit)
    +_pivot_weight_map()
    +_csv_frozenset(name, default)
  }
  class config_py_PivotPolicyConfig {
    <<class>>
    +_installed_share()
    +_resolve_dir(env_key, dirname)
    +_resolve_writable_dir(env_key, dirname)
    +ensure_data_dirs()
    +ensure_reports_dir()
    +_env_tool_allowlist()
    +contact_level(contact)
    +effective_proxies(explicit)
    +_pivot_weight_map()
    +_csv_frozenset(name, default)
  }
  class config_py_PivotConfig {
    <<class>>
    +_installed_share()
    +_resolve_dir(env_key, dirname)
    +_resolve_writable_dir(env_key, dirname)
    +ensure_data_dirs()
    +ensure_reports_dir()
    +_env_tool_allowlist()
    +contact_level(contact)
    +effective_proxies(explicit)
    +_pivot_weight_map()
    +_csv_frozenset(name, default)
  }
  class config_py_StreamConfig {
    <<class>>
    +_installed_share()
    +_resolve_dir(env_key, dirname)
    +_resolve_writable_dir(env_key, dirname)
    +ensure_data_dirs()
    +ensure_reports_dir()
    +_env_tool_allowlist()
    +contact_level(contact)
    +effective_proxies(explicit)
    +_pivot_weight_map()
    +_csv_frozenset(name, default)
  }
  class config_py_ReconFusionConfig {
    <<class>>
    +_installed_share()
    +_resolve_dir(env_key, dirname)
    +_resolve_writable_dir(env_key, dirname)
    +ensure_data_dirs()
    +ensure_reports_dir()
    +_env_tool_allowlist()
    +contact_level(contact)
    +effective_proxies(explicit)
    +_pivot_weight_map()
    +_csv_frozenset(name, default)
  }
  class config_py_SchemaConfig {
    <<class>>
    +_installed_share()
    +_resolve_dir(env_key, dirname)
    +_resolve_writable_dir(env_key, dirname)
    +ensure_data_dirs()
    +ensure_reports_dir()
    +_env_tool_allowlist()
    +contact_level(contact)
    +effective_proxies(explicit)
    +_pivot_weight_map()
    +_csv_frozenset(name, default)
  }
  class config_py_WebConfig {
    <<class>>
    +_installed_share()
    +_resolve_dir(env_key, dirname)
    +_resolve_writable_dir(env_key, dirname)
    +ensure_data_dirs()
    +ensure_reports_dir()
    +_env_tool_allowlist()
    +contact_level(contact)
    +effective_proxies(explicit)
    +_pivot_weight_map()
    +_csv_frozenset(name, default)
  }
  class config_py_RetryConfig {
    <<class>>
    +_installed_share()
    +_resolve_dir(env_key, dirname)
    +_resolve_writable_dir(env_key, dirname)
    +ensure_data_dirs()
    +ensure_reports_dir()
    +_env_tool_allowlist()
    +contact_level(contact)
    +effective_proxies(explicit)
    +_pivot_weight_map()
    +_csv_frozenset(name, default)
  }
  class discoverer_py_DiscoverJob {
    <<class>>
    +_new_job_id()
    +create_discover_job(seed_type, seed_value)
    +start_discover(seed_type, seed_value)
    +start_discover_threadsafe(loop, seed_type, seed_value)
    +_run_discoverer(job)
    +list_jobs(limit)
    +stop(self)
    +should_stop(self)
    +push_event(self, ev)
    +__init__(self, job)
  }
  class discoverer_py__DiscoverJobSink {
    <<class>>
    +_new_job_id()
    +create_discover_job(seed_type, seed_value)
    +start_discover(seed_type, seed_value)
    +start_discover_threadsafe(loop, seed_type, seed_value)
    +_run_discoverer(job)
    +list_jobs(limit)
    +stop(self)
    +should_stop(self)
    +push_event(self, ev)
    +__init__(self, job)
  }
  class entity_extraction_py_Entity {
    <<class>>
    +_looks_like_phone(query)
    +normalize_query(query)
    +detect_query_type(query)
    +_is_valid_domain(candidate)
    +_context(text, start, end, window)
    +extract_from_text(text, source)
    +_ip_in_textual_context(text, idx)
    +extract_from_json(payload, source)
    +_clean_scalar(value)
    +_looks_like_person(value)
  }
  class entity_resolution_py_MatchScore {
    <<class>>
    +jaro(s1, s2)
    +jaro_winkler(s1, s2, prefix_weight)
    +_soundex(token)
    +_normalize_domain(value)
    +_normalize_name(value)
    +normalize_value(etype, value)
    +canonical_id(etype, normalized)
    +blocking_keys(etype, normalized, value)
    +score_pair(etype, a_value, b_value, a_norm, b_norm)
    +_script_of(value)
  }
  class entity_resolution_py_CanonicalEntity {
    <<class>>
    +jaro(s1, s2)
    +jaro_winkler(s1, s2, prefix_weight)
    +_soundex(token)
    +_normalize_domain(value)
    +_normalize_name(value)
    +normalize_value(etype, value)
    +canonical_id(etype, normalized)
    +blocking_keys(etype, normalized, value)
    +score_pair(etype, a_value, b_value, a_norm, b_norm)
    +_script_of(value)
  }
  class entity_resolution_py_SameAsLink {
    <<class>>
    +jaro(s1, s2)
    +jaro_winkler(s1, s2, prefix_weight)
    +_soundex(token)
    +_normalize_domain(value)
    +_normalize_name(value)
    +normalize_value(etype, value)
    +canonical_id(etype, normalized)
    +blocking_keys(etype, normalized, value)
    +score_pair(etype, a_value, b_value, a_norm, b_norm)
    +_script_of(value)
  }
  class entity_resolution_py_ResolutionResult {
    <<class>>
    +jaro(s1, s2)
    +jaro_winkler(s1, s2, prefix_weight)
    +_soundex(token)
    +_normalize_domain(value)
    +_normalize_name(value)
    +normalize_value(etype, value)
    +canonical_id(etype, normalized)
    +blocking_keys(etype, normalized, value)
    +score_pair(etype, a_value, b_value, a_norm, b_norm)
    +_script_of(value)
  }
  class entity_resolution_py__UnionFind {
    <<class>>
    +jaro(s1, s2)
    +jaro_winkler(s1, s2, prefix_weight)
    +_soundex(token)
    +_normalize_domain(value)
    +_normalize_name(value)
    +normalize_value(etype, value)
    +canonical_id(etype, normalized)
    +blocking_keys(etype, normalized, value)
    +score_pair(etype, a_value, b_value, a_norm, b_norm)
    +_script_of(value)
  }
  class entity_resolution_py_EntityResolver {
    <<class>>
    +jaro(s1, s2)
    +jaro_winkler(s1, s2, prefix_weight)
    +_soundex(token)
    +_normalize_domain(value)
    +_normalize_name(value)
    +normalize_value(etype, value)
    +canonical_id(etype, normalized)
    +blocking_keys(etype, normalized, value)
    +score_pair(etype, a_value, b_value, a_norm, b_norm)
    +_script_of(value)
  }
  class entity_store_py_EntityStore {
    <<class>>
    +open_store(path)
    +lookup(self, etype, normalized, aliases)
    +upsert(self, entity)
    +stats(self)
  }
  class event_bus_py_EventBus {
    <<class>>
    +get_bus()
    +__init__(self)
    +subscribe(self, event, handler)
    +unsubscribe(self, event, handler)
    +publish(self, event, payload)
    +clear(self, event)
    +subscriber_count(self, event)
    +_check_event(self, event)
  }
  class feeds_py_FeedPoint {
    <<class>>
    +list_feeds()
    +get_feed(name)
    +fetch_all(bbox, use_cache)
    +to_dict(self)
    +__init__(self)
    +_fetch(self)
    +fetch(self)
    +point(self, record)
    +_fetch(self)
    +_fetch(self)
  }
  class feeds_py_Feed {
    <<class>>
    +list_feeds()
    +get_feed(name)
    +fetch_all(bbox, use_cache)
    +to_dict(self)
    +__init__(self)
    +_fetch(self)
    +fetch(self)
    +point(self, record)
    +_fetch(self)
    +_fetch(self)
  }
  class feeds_py_EarthquakesFeed {
    <<class>>
    +list_feeds()
    +get_feed(name)
    +fetch_all(bbox, use_cache)
    +to_dict(self)
    +__init__(self)
    +_fetch(self)
    +fetch(self)
    +point(self, record)
    +_fetch(self)
    +_fetch(self)
  }
```

---

## Code Property Graph

Machine-readable Code Property Graph (CPG) in JSON-LD format. This block allows AI agents to parse the full structural graph without additional file reads. Compatible with GraphRAG pipelines.

```json
{"@context": "https://schema.org", "analysis": {"communities": [{"cohesion": 0.508, "id": 0, "label": "estorides_core: estorides_web", "size": 27}, {"cohesion": 0.47, "id": 1, "label": "estorides_core: parsers", "size": 24}, {"cohesion": 0.657, "id": 2, "label": "estorides_core: hypothesis_engine", "size": 19}, {"cohesion": 0.449, "id": 3, "label": "estorides_core: estorides", "size": 17}, {"cohesion": 0.373, "id": 4, "label": "estorides_core: config", "size": 17}, {"cohesion": 0.682, "id": 5, "label": "estorides_core: graph_bundle", "size": 15}, {"cohesion": 1.0, "id": 6, "label": "estorides_core: people_intel", "size": 15}, {"cohesion": 0.457, "id": 7, "label": "estorides_export", "size": 15}, {"cohesion": 0.462, "id": 8, "label": "estorides_core: tool_install", "size": 12}, {"cohesion": 1.0, "id": 9, "label": "estorides_core: source_health_monitoring", "size": 3}], "god_nodes": [{"node_id": "estorides_core/config.py", "score": 88.9}, {"node_id": "estorides_web.py", "score": 83.7}, {"node_id": "estorides_core/orchestrator.py", "score": 57.8}, {"node_id": "estorides_cli.py", "score": 33.1}, {"node_id": "estorides_core/entity_extraction.py", "score": 30.1}, {"node_id": "estorides_core/__init__.py", "score": 28.0}, {"node_id": "estorides_core/tool_runner.py", "score": 25.5}, {"node_id": "static/js/estorides.js", "score": 24.0}, {"node_id": "estorides_core/cases.py", "score": 22.1}, {"node_id": "estorides_core/fusion_store.py", "score": 21.8}], "surprising_connections": [{"hops": 7, "source": "estorides_core/lan_inventory.py", "target": "tests/properties/test_change_detection_properties.py"}, {"hops": 7, "source": "estorides_core/lan_inventory.py", "target": "tests/properties/test_hypothesis_engine_properties.py"}, {"hops": 7, "source": "tests/properties/test_change_detection_properties.py", "target": "tests/properties/test_graph_bundle_properties.py"}, {"hops": 7, "source": "tests/properties/test_change_detection_properties.py", "target": "tests/test_recon_report.py"}, {"hops": 7, "source": "tests/properties/test_graph_bundle_properties.py", "target": "tests/properties/test_hypothesis_engine_properties.py"}]}, "edges": [{"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gb_shots.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gb_shots.py", "target": "selenium"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gb_shots.py", "target": "selenium.webdriver.chrome.service"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gb_shots.py", "target": "selenium.webdriver.common.by"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_bridge.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_bridge.py", "target": "selenium"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_bridge.py", "target": "selenium.webdriver.common.by"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_bridge.py", "target": "selenium.webdriver.chrome.service"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_click.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_click.py", "target": "selenium"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_click.py", "target": "selenium.webdriver.common.by"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_click.py", "target": "selenium.webdriver.chrome.service"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_click.py", "target": "selenium.webdriver.common.action_chains"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_data.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_data.py", "target": "json"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_data.py", "target": "selenium"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_data.py", "target": "selenium.webdriver.common.by"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_data.py", "target": "selenium.webdriver.chrome.service"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_debug.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_debug.py", "target": "selenium"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_debug.py", "target": "selenium.webdriver.common.by"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_debug.py", "target": "selenium.webdriver.chrome.service"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_dom.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_dom.py", "target": "selenium"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_dom.py", "target": "selenium.webdriver.common.by"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_dom.py", "target": "selenium.webdriver.chrome.service"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_func.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_func.py", "target": "selenium"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_func.py", "target": "selenium.webdriver.common.by"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_func.py", "target": "selenium.webdriver.chrome.service"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_raw.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_raw.py", "target": "selenium"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_raw.py", "target": "selenium.webdriver.common.by"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_raw.py", "target": "selenium.webdriver.chrome.service"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_shots.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_shots.py", "target": "json"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_shots.py", "target": "selenium"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_shots.py", "target": "selenium.webdriver.chrome.service"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_shots.py", "target": "selenium.webdriver.common.by"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_shots.py", "target": "selenium.webdriver.common.action_chains"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_step.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_step.py", "target": "selenium"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_step.py", "target": "selenium.webdriver.common.by"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_step.py", "target": "selenium.webdriver.chrome.service"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_time.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_time.py", "target": "selenium"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_time.py", "target": "selenium.webdriver.common.by"}, {"confidence": "EXTRACTED", "relation": "imports", "source": ".scratchpad/gfv2_time.py", "target": "selenium.webdriver.chrome.service"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "app.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "app.py", "target": "warnings"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "app.py", "target": "wsgi"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "argparse"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "asyncio"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "json"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "sys"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "collections.abc"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "estorides_core.config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "estorides_core.knowledge_graph"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "estorides_core.orchestrator"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "estorides_core.validation"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "estorides_export"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "estorides_core.monitoring"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "estorides_core.cases"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "estorides_core.discoverer"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "sys"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "estorides_core.scope"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "networkx"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "networkx"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "estorides_core.cases"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "estorides_export.report"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "estorides_core.cases"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "estorides_core.fusion_store"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "estorides_core.entity_extraction"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "estorides_core.monitoring"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "estorides_core.monitoring"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "estorides_core.monitoring"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "estorides_core.monitoring"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "estorides_core.monitoring"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "estorides_core.monitoring"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "estorides_core.alerter"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "estorides_core.alerter"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "estorides_core.monitoring"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "estorides_core.monitoring"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "estorides_core.monitoring"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "estorides_web"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "estorides_core.entity_extraction"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "networkx"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_cli.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/__init__.py", "target": "config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/active_recon.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/active_recon.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/active_recon.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/active_recon.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/active_recon.py", "target": "tool_runner"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/alerter.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/alerter.py", "target": "json"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/alerter.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/alerter.py", "target": "os"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/alerter.py", "target": "smtplib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/alerter.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/alerter.py", "target": "urllib.error"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/alerter.py", "target": "urllib.request"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/alerter.py", "target": "datetime"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/alerter.py", "target": "email.mime.text"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/alerter.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/alerter.py", "target": "urllib.request"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/alerter.py", "target": "ssrf_guard"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/alerter.py", "target": "datetime"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/async_client.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/async_client.py", "target": "asyncio"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/async_client.py", "target": "hashlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/async_client.py", "target": "json"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/async_client.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/async_client.py", "target": "sqlite3"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/async_client.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/async_client.py", "target": "collections.abc"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/async_client.py", "target": "contextlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/async_client.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/async_client.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/async_client.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/async_client.py", "target": "urllib.parse"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/async_client.py", "target": "aiohttp"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/async_client.py", "target": "config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/async_client.py", "target": "ssrf_guard"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/async_client.py", "target": "requests"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/async_client.py", "target": "config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/async_client.py", "target": "aiohttp_socks"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/audit.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/audit.py", "target": "json"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/audit.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/audit.py", "target": "os"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/audit.py", "target": "threading"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/audit.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/audit.py", "target": "collections"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/audit.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/audit.py", "target": "datetime"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/audit.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/audit.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/audit.py", "target": "config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/case_crypto.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/case_crypto.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/case_crypto.py", "target": "os"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/case_crypto.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/case_crypto.py", "target": "cryptography.fernet"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/case_crypto.py", "target": "cryptography.fernet"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/cases.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/cases.py", "target": "json"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/cases.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/cases.py", "target": "sqlite3"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/cases.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/cases.py", "target": "uuid"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/cases.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/cases.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/cases.py", "target": "case_crypto"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/cases.py", "target": "config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/cases.py", "target": "sqlite_store"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/change_detection.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/change_detection.py", "target": "collections.abc"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/change_detection.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/change_detection.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/change_detection.py", "target": "ids"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/change_detection.py", "target": "reliability_scoring"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/cloud_asset_discovery.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/cloud_asset_discovery.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/cloud_asset_discovery.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/cloud_asset_discovery.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/code_exposure.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/code_exposure.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/code_exposure.py", "target": "re"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/code_exposure.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/code_exposure.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/config.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/config.py", "target": "os"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/config.py", "target": "sys"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/config.py", "target": "collections.abc"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/config.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/config.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/config.py", "target": "envutil"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/config.py", "target": "envutil"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/config.py", "target": "envutil"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/config.py", "target": "envutil"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/discoverer.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/discoverer.py", "target": "asyncio"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/discoverer.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/discoverer.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/discoverer.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/discoverer.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/discoverer.py", "target": "cases"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/discoverer.py", "target": "config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/discoverer.py", "target": "job_registry"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/discoverer.py", "target": "pivot_engine"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/discoverer.py", "target": "estorides_core.graph_kuzu"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/discoverer.py", "target": "estorides_core.orchestrator"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/discoverer.py", "target": "orchestrator"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/entity_extraction.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/entity_extraction.py", "target": "json"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/entity_extraction.py", "target": "re"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/entity_extraction.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/entity_extraction.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/entity_extraction.py", "target": "config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/entity_extraction.py", "target": "difflib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/entity_extraction.py", "target": "urllib.parse"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/entity_resolution.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/entity_resolution.py", "target": "ipaddress"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/entity_resolution.py", "target": "re"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/entity_resolution.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/entity_resolution.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/entity_resolution.py", "target": "config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/entity_resolution.py", "target": "entity_extraction"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/entity_resolution.py", "target": "ids"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/entity_resolution.py", "target": "transliteration"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/entity_store.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/entity_store.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/entity_store.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/entity_store.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/entity_store.py", "target": "config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/entity_store.py", "target": "entity_resolution"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/entity_store.py", "target": "sqlite_store"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/event_bus.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/event_bus.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/event_bus.py", "target": "re"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/event_bus.py", "target": "collections.abc"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/event_bus.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/feeds.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/feeds.py", "target": "json"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/feeds.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/feeds.py", "target": "os"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/feeds.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/feeds.py", "target": "abc"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/feeds.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/feeds.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/feeds.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/feeds.py", "target": "config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/feeds.py", "target": "ssrf_guard"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/feeds.py", "target": "requests"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/feeds.py", "target": "csv"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/feeds.py", "target": "io"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/feeds.py", "target": "requests"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/feeds.py", "target": "requests"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/fusion_analytics.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/fusion_analytics.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/fusion_analytics.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/fusion_analytics.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/fusion_store.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/fusion_store.py", "target": "json"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/fusion_store.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/fusion_store.py", "target": "sqlite3"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/fusion_store.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/fusion_store.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/fusion_store.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/fusion_store.py", "target": "config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/fusion_store.py", "target": "ids"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/fusion_store.py", "target": "reliability_scoring"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/fusion_store.py", "target": "sqlite_store"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/fusion_store.py", "target": "entity_resolution"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/graph_bundle.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/graph_bundle.py", "target": "math"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/graph_bundle.py", "target": "collections.abc"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/graph_bundle.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/graph_bundle.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/graph_bundle.py", "target": "estorides_core.envutil"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/graph_bundle.py", "target": "estorides_core.graph_force"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/graph_force.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/graph_force.py", "target": "math"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/graph_force.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/graph_kuzu.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/graph_kuzu.py", "target": "json"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/graph_kuzu.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/graph_kuzu.py", "target": "os"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/graph_kuzu.py", "target": "threading"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/graph_kuzu.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/graph_kuzu.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/graph_kuzu.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/graph_kuzu.py", "target": "kuzu"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/graph_kuzu.py", "target": "config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/graph_rag_search.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/graph_rag_search.py", "target": "math"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/graph_rag_search.py", "target": "re"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/graph_rag_search.py", "target": "collections.abc"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/graph_rag_search.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/graph_rag_search.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/graph_rag_search.py", "target": "estorides_core.envutil"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/hypothesis_engine.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/hypothesis_engine.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/hypothesis_engine.py", "target": "collections"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/hypothesis_engine.py", "target": "collections.abc"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/hypothesis_engine.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/hypothesis_engine.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/hypothesis_engine.py", "target": "ids"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/hypothesis_engine.py", "target": "reliability_scoring"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/hypothesis_engine.py", "target": "reliability_scoring"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/ids.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/ids.py", "target": "hashlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/intel_resolver.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/intel_resolver.py", "target": "ipaddress"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/intel_resolver.py", "target": "json"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/intel_resolver.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/intel_resolver.py", "target": "os"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/intel_resolver.py", "target": "re"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/intel_resolver.py", "target": "threading"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/intel_resolver.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/intel_resolver.py", "target": "collections"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/intel_resolver.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/intel_resolver.py", "target": "requests"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/intel_resolver.py", "target": "config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/intel_resolver.py", "target": "ssrf_guard"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/intel_resolver.py", "target": "ontology"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/job_registry.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/job_registry.py", "target": "threading"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/job_registry.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/job_registry.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/knowledge_graph.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/knowledge_graph.py", "target": "json"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/knowledge_graph.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/knowledge_graph.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/knowledge_graph.py", "target": "collections"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/knowledge_graph.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/knowledge_graph.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/knowledge_graph.py", "target": "networkx"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/knowledge_graph.py", "target": "config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/knowledge_graph.py", "target": "entity_extraction"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/knowledge_graph.py", "target": "networkx.algorithms.community"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_bundle.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_bundle.py", "target": "json"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_bundle.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_bundle.py", "target": "os"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_bundle.py", "target": "re"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_bundle.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_bundle.py", "target": "uuid"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_bundle.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_bundle.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_bundle.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_bundle.py", "target": "config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_bundle.py", "target": "datetime"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_inventory.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_inventory.py", "target": "ipaddress"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_inventory.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_inventory.py", "target": "re"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_inventory.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_inventory.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_inventory.py", "target": "defusedxml.ElementTree"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_policy.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_policy.py", "target": "ipaddress"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_policy.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_policy.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_policy.py", "target": "envutil"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_runner.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_runner.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_runner.py", "target": "shutil"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_runner.py", "target": "subprocess"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_runner.py", "target": "collections.abc"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_runner.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_runner.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_runner.py", "target": "tool_runner"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_scope.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_scope.py", "target": "ipaddress"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_scope.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_scope.py", "target": "re"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_scope.py", "target": "shutil"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_scope.py", "target": "subprocess"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_scope.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_web.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_web.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_web.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/lan_web.py", "target": "ipaddress"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/mitre_attack.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/mitre_attack.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/mitre_attack.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/mitre_attack.py", "target": "json"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/monitoring.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/monitoring.py", "target": "asyncio"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/monitoring.py", "target": "json"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/monitoring.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/monitoring.py", "target": "os"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/monitoring.py", "target": "sqlite3"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/monitoring.py", "target": "threading"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/monitoring.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/monitoring.py", "target": "uuid"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/monitoring.py", "target": "collections.abc"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/monitoring.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/monitoring.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/monitoring.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/monitoring.py", "target": "config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/monitoring.py", "target": "sqlite_store"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/monitoring.py", "target": "concurrent.futures"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/observation_models.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/observation_models.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/observation_models.py", "target": "pydantic"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/observation_models.py", "target": "config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/ontology.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/ontology.py", "target": "csv"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/ontology.py", "target": "io"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/ontology.py", "target": "json"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/ontology.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/ontology.py", "target": "re"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/ontology.py", "target": "threading"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/ontology.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/ontology.py", "target": "collections"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/ontology.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/ontology.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/ontology.py", "target": "urllib.parse"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/ontology.py", "target": "config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/ontology.py", "target": "ssrf_guard"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/ontology.py", "target": "requests"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/ontology.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/openapi.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/openapi.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/ops_observability.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/ops_observability.py", "target": "json"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/ops_observability.py", "target": "re"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/ops_observability.py", "target": "threading"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/ops_observability.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/ops_observability.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/ops_observability.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/ops_observability.py", "target": "config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/orchestrator.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/orchestrator.py", "target": "asyncio"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/orchestrator.py", "target": "json"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/orchestrator.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/orchestrator.py", "target": "os"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/orchestrator.py", "target": "re"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/orchestrator.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/orchestrator.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/orchestrator.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/orchestrator.py", "target": "estorides_llm"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/orchestrator.py", "target": "async_client"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/orchestrator.py", "target": "config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/orchestrator.py", "target": "entity_extraction"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/orchestrator.py", "target": "event_bus"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/orchestrator.py", "target": "knowledge_graph"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/orchestrator.py", "target": "mitre_attack"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/orchestrator.py", "target": "ontology"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/orchestrator.py", "target": "parsers"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/orchestrator.py", "target": "recon_fusion"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/orchestrator.py", "target": "relationship_inference"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/orchestrator.py", "target": "source_loader"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/orchestrator.py", "target": "system_app_sources"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/orchestrator.py", "target": "graph_kuzu"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/orchestrator.py", "target": "cases"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/orchestrator.py", "target": "intel_resolver"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/orchestrator.py", "target": "entity_resolution"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/orchestrator.py", "target": "fusion_store"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/orchestrator.py", "target": "fusion_store"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/orchestrator.py", "target": "entity_store"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/orchestrator.py", "target": "system_app_sources"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/orchestrator.py", "target": "pagination"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/orchestrator.py", "target": "pagination"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/osiris_sources.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/osiris_sources.py", "target": "csv"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/osiris_sources.py", "target": "io"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/osiris_sources.py", "target": "json"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/osiris_sources.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/osiris_sources.py", "target": "re"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/osiris_sources.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/osiris_sources.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/osiris_sources.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/osiris_sources.py", "target": "requests"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/osiris_sources.py", "target": "config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/osiris_sources.py", "target": "ssrf_guard"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/pagination.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/pagination.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/pagination.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/parsers.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/parsers.py", "target": "json"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/parsers.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/parsers.py", "target": "re"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/parsers.py", "target": "collections.abc"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/parsers.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/pdns_monitor.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/pdns_monitor.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/pdns_monitor.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/pdns_monitor.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/people_intel.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/people_intel.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/people_intel.py", "target": "re"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/people_intel.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/people_intel.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/pivot_engine.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/pivot_engine.py", "target": "heapq"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/pivot_engine.py", "target": "itertools"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/pivot_engine.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/pivot_engine.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/pivot_engine.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/pivot_engine.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/pivot_engine.py", "target": "config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/recon_fusion.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/recon_fusion.py", "target": "math"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/recon_fusion.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/recon_fusion.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/recon_fusion.py", "target": "enum"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/recon_fusion.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/recon_fusion.py", "target": "config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/recon_fusion.py", "target": "config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/recon_fusion.py", "target": "ids"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/recon_fusion.py", "target": "reliability_scoring"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/recon_pipeline.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/recon_pipeline.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/recon_pipeline.py", "target": "tech_fingerprint"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/recon_pipeline.py", "target": "vuln_correlation"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/recon_pipeline.py", "target": "cloud_asset_discovery"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/recon_pipeline.py", "target": "people_intel"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/recon_pipeline.py", "target": "code_exposure"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/recon_pipeline.py", "target": "supply_chain"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/recon_pipeline.py", "target": "pdns_monitor"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/recon_pipeline.py", "target": "target_scoring"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/relationship_inference.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/relationship_inference.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/relationship_inference.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/reliability_scoring.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/reliability_scoring.py", "target": "math"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/reliability_scoring.py", "target": "collections.abc"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/reliability_scoring.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/reliability_scoring.py", "target": "enum"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/scope.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/scope.py", "target": "ipaddress"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/scope.py", "target": "json"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/scope.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/scope.py", "target": "re"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/scope.py", "target": "abc"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/scope.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/scope.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/scope.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/search_telemetry.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/search_telemetry.py", "target": "re"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/search_telemetry.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/search_telemetry.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/search_telemetry.py", "target": "config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/socmint.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/socmint.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/socmint.py", "target": "re"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/socmint.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/socmint.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/socmint.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/source_health_monitoring.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/source_health_monitoring.py", "target": "collections.abc"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/source_health_monitoring.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/source_health_monitoring.py", "target": "enum"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/source_health_monitoring.py", "target": "envutil"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/source_health_monitoring.py", "target": "envutil"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/source_loader.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/source_loader.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/source_loader.py", "target": "re"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/source_loader.py", "target": "collections.abc"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/source_loader.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/source_loader.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/source_loader.py", "target": "yaml"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/source_loader.py", "target": "config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/sqlite_store.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/sqlite_store.py", "target": "sqlite3"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/sqlite_store.py", "target": "threading"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/sqlite_store.py", "target": "collections.abc"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/sqlite_store.py", "target": "contextlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/sqlite_store.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/sqlite_store.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/sqlite_store.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/ssrf_guard.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/ssrf_guard.py", "target": "ipaddress"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/ssrf_guard.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/ssrf_guard.py", "target": "os"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/ssrf_guard.py", "target": "socket"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/ssrf_guard.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/ssrf_guard.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/ssrf_guard.py", "target": "urllib.parse"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/supply_chain.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/supply_chain.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/supply_chain.py", "target": "re"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/supply_chain.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/supply_chain.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/system_app_sources.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/system_app_sources.py", "target": "json"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/system_app_sources.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/system_app_sources.py", "target": "platform"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/system_app_sources.py", "target": "re"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/system_app_sources.py", "target": "shutil"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/system_app_sources.py", "target": "tempfile"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/system_app_sources.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/system_app_sources.py", "target": "collections.abc"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/system_app_sources.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/system_app_sources.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/system_app_sources.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/system_app_sources.py", "target": "config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/system_app_sources.py", "target": "parsers"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/system_app_sources.py", "target": "tool_runner"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/system_app_sources.py", "target": "tool_runner"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/tech_fingerprint.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/tech_fingerprint.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/tech_fingerprint.py", "target": "re"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/tech_fingerprint.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/tech_fingerprint.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/tool_install.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/tool_install.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/tool_install.py", "target": "os"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/tool_install.py", "target": "re"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/tool_install.py", "target": "shlex"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/tool_install.py", "target": "shutil"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/tool_install.py", "target": "subprocess"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/tool_install.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/tool_install.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/tool_install.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/tool_install.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/tool_install.py", "target": "yaml"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/tool_install.py", "target": "config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/tool_install.py", "target": "tool_runner"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/tool_install.py", "target": "config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/tool_install.py", "target": "argparse"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/tool_runner.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/tool_runner.py", "target": "hashlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/tool_runner.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/tool_runner.py", "target": "shutil"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/tool_runner.py", "target": "subprocess"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/tool_runner.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/tool_runner.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/tool_runner.py", "target": "config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/tool_runner.py", "target": "entity_extraction"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/transforms.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/transforms.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/transforms.py", "target": "re"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/transforms.py", "target": "collections.abc"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/transforms.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/transforms.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/transforms.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/transforms.py", "target": "config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/transforms.py", "target": "intel_resolver"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/transforms.py", "target": "re"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/transforms.py", "target": "yaml"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/transliteration.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/transliteration.py", "target": "unicodedata"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/transliteration.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/validation.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/validation.py", "target": "re"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/validation.py", "target": "unicodedata"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/validation.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/validation.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/validation.py", "target": "entity_extraction"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/vuln_correlation.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/vuln_correlation.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/vuln_correlation.py", "target": "re"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/vuln_correlation.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/vuln_correlation.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/web_security.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/web_security.py", "target": "hmac"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/web_security.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/web_security.py", "target": "os"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/web_security.py", "target": "secrets"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/web_security.py", "target": "collections.abc"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/web_security.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/web_security.py", "target": "functools"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/web_security.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/web_security.py", "target": "urllib.parse"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/web_security.py", "target": "flask"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/web_security.py", "target": "envutil"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/web_security.py", "target": "envutil"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_core/web_security.py", "target": "flask"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/__init__.py", "target": "stix"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/__init__.py", "target": "misp"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/__init__.py", "target": "encryption"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/__init__.py", "target": "report"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/__init__.py", "target": "recon_report"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/encryption.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/encryption.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/encryption.py", "target": "shutil"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/encryption.py", "target": "subprocess"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/encryption.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/encryption.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/encryption.py", "target": "estorides_core.knowledge_graph"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/encryption.py", "target": "estorides_export"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/encryption.py", "target": "estorides_export"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/misp.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/misp.py", "target": "json"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/misp.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/misp.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/misp.py", "target": "uuid"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/misp.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/misp.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/misp.py", "target": "estorides_core.config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/misp.py", "target": "estorides_core.knowledge_graph"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/recon_report.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/recon_report.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/recon_report.py", "target": "re"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/recon_report.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/recon_report.py", "target": "datetime"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/recon_report.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/report.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/report.py", "target": "datetime"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/report.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/report.py", "target": "collections"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/report.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/stix.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/stix.py", "target": "json"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/stix.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/stix.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/stix.py", "target": "uuid"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/stix.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/stix.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/stix.py", "target": "estorides_core.config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_export/stix.py", "target": "estorides_core.knowledge_graph"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_llm/__init__.py", "target": "manager"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_llm/intelligence_prompts.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_llm/intelligence_prompts.py", "target": "json"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_llm/intelligence_prompts.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_llm/manager.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_llm/manager.py", "target": "json"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_llm/manager.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_llm/manager.py", "target": "os"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_llm/manager.py", "target": "collections.abc"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_llm/manager.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_llm/manager.py", "target": "requests"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_llm/manager.py", "target": "estorides_core.config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_llm/manager.py", "target": "estorides_llm.intelligence_prompts"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "asyncio"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "json"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "os"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "queue"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "shutil"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "sys"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "tempfile"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "threading"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "collections.abc"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "functools"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "flask"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core.audit"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core.config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core.discoverer"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core.discoverer"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core.entity_extraction"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core.feeds"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core.job_registry"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core.knowledge_graph"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core.lan_web"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core.lan_web"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core.openapi"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core.ops_observability"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core.orchestrator"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core.pivot_engine"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core.search_telemetry"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core.validation"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core.web_security"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_export"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_export.encryption"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_web_tools"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "networkx"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core.graph_rag_search"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core.knowledge_graph"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core.lan_web"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core.entity_extraction"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "networkx"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "networkx"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core.cases"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core.graph_kuzu"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core.intel_resolver"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core.fusion_store"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core.socmint"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core.monitoring"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core.monitoring"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core.monitoring"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core.alerter"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core.monitoring"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core.transforms"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core.transforms"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core.graph_force"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core.fusion_analytics"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core.entity_extraction"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core.graph_kuzu"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web.py", "target": "estorides_core.orchestrator"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web_tools.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web_tools.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web_tools.py", "target": "threading"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web_tools.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web_tools.py", "target": "flask"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web_tools.py", "target": "estorides_core.audit"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web_tools.py", "target": "estorides_core.tool_install"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web_tools.py", "target": "estorides_core.web_security"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "estorides_web_tools.py", "target": "estorides_web"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "function"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "querySelector"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "from"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setVisible"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "removeProperty"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "isLanQueryLocal"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "trim"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "test"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "test"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "test"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "test"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "test"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "lanRunFields"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "ensureLanConsent"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "resolve"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "resolve"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "resolve"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "purifyHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setSanitizedHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setVisible"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setVisible"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "removeEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "removeEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "resolve"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "done"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "done"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "installAuthFetch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "querySelector"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "trim"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "bind"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "function"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "set"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "set"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "origFetch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "readTelemetry"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "parse"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "assign"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "helpers"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "detectQueryTypeLocal"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "trim"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "test"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "test"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "test"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showToast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setTimeout"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setTimeout"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "remove"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "updateQueryChip"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "remove"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setRunProgress"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setVisible"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "min"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setVisible"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "removeAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showEmptyState"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setVisible"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setVisible"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "summariseObservation"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "truncate"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "error"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "item"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "slice"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "truncate"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "keys"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "slice"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "map"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "isArray"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "truncate"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "buildResultCard"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "summariseObservation"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "random"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "toString"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "slice"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "map"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "join"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "attempt"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "querySelector"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "toggle"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "querySelector"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "stopPropagation"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "toggle"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "contains"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "querySelector"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "stopPropagation"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "requestToolInstall"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "requestToolInstall"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "fetch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "stringify"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showToast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "json"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "pollToolInstall"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showToast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "pollToolInstall"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "fetch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "json"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setTimeout"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "pollToolInstall"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showToast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "_installErrMsg"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showToast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showToast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "_installErrMsg"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "toString"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "replace"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "trim"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "slice"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "message"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "populateCategoryFilter"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "map"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "applyResultFilters"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "toLowerCase"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "toLowerCase"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "includes"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "bindResultFilters"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showFriendlyError"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "remove"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "retryFn"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "focusGraphNodeByValue"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "find"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "focusNode"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "selectNode"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "switchSidebarTab"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "click"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "switchCanvasTab"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "click"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "map"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setView"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "instead"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "tileLayer"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addTo"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "tileLayer"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addTo"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "clearMap"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "removeLayer"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "plotPoints"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "clearMap"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "circleMarker"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "bindPopup"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeAttr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeAttr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addTo"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setView"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "fitBounds"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "buttons"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "preventDefault"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "expandNode"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "remove"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "remove"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "remove"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "remove"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "invalidateSize"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "drawGraph"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "runQuery"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "updateQueryChip"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "clearAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "replotStreamData"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "renderTimeline"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "now"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "plotPoints"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "renderTimeline"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "stopRunStream"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "fetch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "close"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "scope"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "renderLanResults"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showEmptyState"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "device"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "keys"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "bundle"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "startLanScan"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "trim"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "fetch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "json"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showFriendlyError"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "ensureLanConsent"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showToast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showWorkingIndicator"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "fetch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "json"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatusDot"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showFriendlyError"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "renderLanResults"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showToast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showToast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatusDot"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "drawGraph"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatusDot"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showFriendlyError"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "hideWorkingIndicator"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "runQuery"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "trim"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "ensureLanConsent"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showToast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "updateQueryChip"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showEmptyState"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setRunProgress"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showWorkingIndicator"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "bindResultFilters"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "stopRunStream"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "now"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "clearMap"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "fetch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "json"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatusDot"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showFriendlyError"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setRunProgress"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "hideWorkingIndicator"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "runQueryBlocking"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "parse"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "handleRunStreamEvent"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "round"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "floor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatusDot"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setRunProgress"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showToast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "stopRunStream"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "hideWorkingIndicator"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setTimeout"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "function"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatusDot"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "runQueryBlocking"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showWorkingIndicator"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "fetch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "json"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showFriendlyError"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "runQueryBlocking"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "hideWorkingIndicator"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "renderResult"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "renderTieredResults"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatusDot"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showToast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatusDot"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showFriendlyError"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "runQueryBlocking"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setRunProgress"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "hideWorkingIndicator"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "searchEntity"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "ensureLanConsent"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showToast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "hideWorkingIndicator"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "stopRunStream"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showWorkingIndicator"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "trim"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "fetch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "json"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatusDot"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "hideWorkingIndicator"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "parse"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "handleRunStreamEvent"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatusDot"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showToast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "stopRunStream"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "hideWorkingIndicator"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "function"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatusDot"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "handleRunStreamEvent"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatusDot"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatusDot"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendStreamObservation"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendStreamEntity"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "loadAnalysisModels"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "renderAnalysis"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "renderGraphSummary"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatusDot"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendStreamObservation"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "buildResultCard"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "populateCategoryFilter"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "applyResultFilters"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setRunProgress"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showToast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "sources"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "replotStreamData"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendStreamEntity"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "dispatchEvent"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "clearAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "stopRunStream"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "clearMap"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "remove"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "stop"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "updateQueryChip"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setRunProgress"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showEmptyState"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatusDot"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "renderResult"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showEmptyState"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "bindResultFilters"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "slice"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "populateCategoryFilter"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "applyResultFilters"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "renderEntities"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "loadAnalysisModels"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "renderAnalysis"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "renderGraphSummary"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "plotPoints"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "renderTimeline"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "run"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "loadAnalysisModels"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "fetch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "json"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "isArray"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "renderAnalysisModels"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "renderAnalysisModels"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "makeModelPill"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "makeModelPill"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "reanalyze"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "renderAnalysis"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "renderMarkdownInto"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setThinkingVisible"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "renderer"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "renderMarkdownInto"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "parse"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setSanitizedHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setThinkingVisible"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "toggleThinking"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "reanalyze"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "abort"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "box"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "trim"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "slice"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "fetch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getReader"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "requestAnimationFrame"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "renderMarkdownInto"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "split"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "pop"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "trim"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "parse"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "scheduleRender"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "read"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "flush"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "decode"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "flush"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "pump"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "pump"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "finally"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "data"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "analyseEntity"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "switchSidebarTab"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "loadAnalysisModels"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "renderAnalysis"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "reanalyze"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "server"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "expandNode"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "expandNode"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "fetch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "encodeURIComponent"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "json"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "mergeExpansionIntoGraph"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "mergeExpansionIntoGraph"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "shift"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "drawGraphWithExtras"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "parseFloat"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "parseFloat"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "parseFloat"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "circleMarker"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "bindPopup"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addTo"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "drawGraph"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "understands"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "drawGraphWithExtras"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "fetch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "json"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "pushLink"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "pushLink"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "pushLink"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "engine"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "deriveClusters"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "enough"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "renderGraphCore"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "is3DActive"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "intelligence"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "resolverTypeFor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "toLowerCase"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "return"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "overrides"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "parse"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "saveLevelOverrides"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setItem"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "levelOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "clusterColor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "find"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "safeColor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "data"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "rgb"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "safeColor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "trim"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "test"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "list"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "deriveClusters"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "safeColor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "clusterColor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "values"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "overlays"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "hideTooltip"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setVisible"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "sanitizer"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "elements"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "purifyHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "sanitize"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "text"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setSanitizedHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "purifyHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showTooltipAt"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getBoundingClientRect"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setSanitizedHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "paint"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setVisible"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "hideContextMenu"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setVisible"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "cluster"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showBridgeTooltip"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "clusterColor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "clusterColor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "find"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showTooltipAt"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "labelFor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "labelFor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showNodeTooltip"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showTooltipAt"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "levelOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "levelOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "replace"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "querySelector"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "stopPropagation"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "hideTooltip"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "analyseEntity"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showContextMenu"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "resolverTypeFor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getBoundingClientRect"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "map"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "replace"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "join"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setVisible"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "querySelector"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "hideContextMenu"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "resolverTypeFor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "expandNode"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "querySelector"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "hideContextMenu"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "searchEntity"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "querySelector"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "hideContextMenu"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "focusNode"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "querySelector"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "hideContextMenu"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "analyseEntity"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setNodeLevel"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "hideContextMenu"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "fetch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "json"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "querySelector"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "remove"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "insertAdjacentHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "insertAdjacentHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "hideContextMenu"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "runTransformStream"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "runTransform"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setNodeLevel"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "saveLevelOverrides"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "applyLevelStyles"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "selectNode"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "applyLevelStyles"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "select"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "attr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "levelOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "attr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "levelOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "attr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "levelOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "focusNode"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "max"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "translate"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "scale"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "transition"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "duration"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "call"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "select"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "attr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "runTransform"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "fetch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "json"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "mergeExpansionIntoGraph"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "history"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "runTransformStream"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "encodeURIComponent"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "encodeURIComponent"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "encodeURIComponent"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "mergeExpansionIntoGraph"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "splice"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "fetch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getReader"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "read"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "decode"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "slice"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "slice"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "split"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "slice"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "trim"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "slice"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "trim"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "flush"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "merged"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "flush"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "flush"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "merged"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "expansion"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "undoGraph"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "pop"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "drawGraphWithExtras"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "preventDefault"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "undoGraph"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "selectNode"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setVisible"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "resolverTypeFor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addText"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addText"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "replace"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addText"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "replace"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "function"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setNodeLevel"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "keys"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "filter"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "slice"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addText"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addText"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setNodeLevel"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "fetch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "json"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "insertAdjacentHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "runTransformStream"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "runTransform"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "module"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "function"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "renderGraphCore"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "renderer"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "renderGraphCore"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "remove"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "hideContextMenu"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "hideTooltip"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "map"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "filter"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "frame"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "max"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "select"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "attr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "attr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "classed"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "call"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "scaleExtent"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "attr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "hideContextMenu"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "hideTooltip"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "attr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "attr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "attr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "attr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forceSimulation"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "force"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "distance"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "strength"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "force"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "strength"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "force"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "force"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "selectAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "data"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "enter"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "attr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "attr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "attr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "attr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "attr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "style"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "stopPropagation"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showBridgeTooltip"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "selectAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "data"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "enter"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "attr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "style"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "call"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "alphaTarget"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "restart"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "alphaTarget"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "attr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "attr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "attr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "safeColor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "attr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "levelOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "attr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "levelOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "attr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "levelOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "stopPropagation"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "selectNode"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "resolverTypeFor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "expandNode"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "stopPropagation"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "focusNode"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "preventDefault"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "stopPropagation"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "hideTooltip"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showContextMenu"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showNodeTooltip"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "selectAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "data"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "enter"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "attr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "attr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "attr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "text"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "drawHulls"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "keys"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "map"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "polygonHull"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "filter"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "selectAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "data"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "enter"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "attr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "merge"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "attr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "join"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "attr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "clusterColor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "attr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "clusterColor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "exit"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "remove"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "attr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "attr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "attr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "attr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "attr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "translate"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "attr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "attr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "drawHulls"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "list"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "_redrawGraph"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "renderGraphCore"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "enhancements"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatusDot"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showWorkingIndicator"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatusDot"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "hideWorkingIndicator"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatusDot"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "remove"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "toggleTierSection"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "renderTieredResults"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "keys"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "some"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "return"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createDocumentFragment"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "toggleTierSection"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "preventDefault"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "toggleTierSection"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "toFixed"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "map"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "join"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "insertBefore"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeAttr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "replace"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "replace"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "code"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "buildMapCoords"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "observations"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "parseFloat"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "colorFor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "parseFloat"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "colorFor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "split"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "map"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "match"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "entities"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "itself"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "parseFloat"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "parseFloat"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "colorForKind"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "colorForKind"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "validCoord"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "isFinite"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "isFinite"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "abs"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "abs"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "colorFor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "renderEntities"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "renderEntities"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "trim"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "toLowerCase"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "filter"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "toLowerCase"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "includes"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "toLowerCase"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "includes"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "slice"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "preventDefault"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "remove"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "expandNode"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setTimeout"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "focusGraphNodeByValue"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "querySelector"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "stopPropagation"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "expandNode"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "renderGraphSummary"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "toFixed"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "entities"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "slice"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "colorForKind"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "toFixed"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "colorForKind"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "return"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "renderTimeline"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "slice"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "missing"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "now"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "sort"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "toISOString"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "replace"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "replace"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setVisible"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "fmtTime"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "toISOString"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "replace"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "substring"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "fmtTime"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "fmtTime"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "filterTimeline"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "parseFloat"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "remove"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "function"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "parseInt"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "filterTimeline"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "function"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "clearInterval"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "filterTimeline"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setInterval"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "parseInt"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "clearInterval"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "filterTimeline"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "drawGraph"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "fetch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "json"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "renderGraphCore"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "wireGraphOverlays"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setVisible"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "hideContextMenu"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "hideContextMenu"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "hideTooltip"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "loadCases"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "loadCases"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "fetch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "json"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "map"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "join"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "fetch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "json"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "openCaseDetail"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "record"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "openCaseDetail"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "slice"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "slice"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "map"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "join"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "map"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "join"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "toISOString"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "slice"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "replace"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "openModal"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "slice"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "switchSidebarTab"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "loadAnalysisModels"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "renderAnalysis"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "reanalyze"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "restoreCaseToWorkspace"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "querySelector"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "tabs"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "restoreCaseToWorkspace"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showEmptyState"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "renderEntities"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "map"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "renderGraphSummary"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "coordinates"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "buildCaseMapCoords"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "plotPoints"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "renderTimeline"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showToast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "buildCaseMapCoords"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "parseFloat"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "parseFloat"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "centroids"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "toUpperCase"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "renderCaseItem"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "toISOString"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "slice"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "replace"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "indexOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "return"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "debounce"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "function"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "clearTimeout"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setTimeout"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "apply"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "trim"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "fetch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "encodeURIComponent"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "json"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "stringify"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "parse"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "probes"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "keys"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "promptModal"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "fetch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "json"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "stringify"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "replace"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "replace"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "replace"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "replace"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "truncate"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "slice"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "actions"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "renderCaseItem"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "closest"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "caseActionSave"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "caseActionDiff"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "caseActionReport"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "caseActionSave"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "promptModal"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "fetch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "stringify"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "json"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "loadCases"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showToast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "caseActionDiff"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "promptModal"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "fetch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "encodeURIComponent"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "json"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "renderCaseDiffPanel"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showToast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "list"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "renderCaseDiffPanel"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "slice"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "map"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "join"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "slice"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "map"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "join"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "remove"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "caseActionReport"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "fetch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "json"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "entries"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "sort"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "slice"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showReportModal"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showToast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showReportModal"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "remove"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "report"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "remove"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "remove"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "helpers"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "_sanitizeInput"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "replace"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "openModal"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "remove"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "map"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "join"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "remove"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "onClose"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "querySelector"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "close"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "onClick"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "close"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "promptModal"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "openModal"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "resolve"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "resolve"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "resolve"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "querySelector"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "resolve"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "remove"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setTimeout"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "focus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "confirmModal"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "openModal"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "resolve"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "resolve"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "resolve"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showEmptyState"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "loadAnalysisModels"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "bindResultFilters"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "updateQueryChip"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "focus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setTimeout"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "initOnboarding"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getItem"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setVisible"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setItem"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setVisible"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setItem"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setVisible"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "preventDefault"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "focus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "preventDefault"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setVisible"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setVisible"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setVisible"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "clearAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "preventDefault"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "runQuery"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "preventDefault"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "switchSidebarTab"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "preventDefault"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "switchCanvasTab"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "preventDefault"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "switchCanvasTab"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "preventDefault"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "switchCanvasTab"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setVisible"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setVisible"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "loadSidebarWidth"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getItem"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "parseInt"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "saveSidebarWidth"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setItem"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "loadSidebarCollapsed"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "saveSidebarCollapsed"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setItem"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "loadSidebarWidth"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "loadSidebarCollapsed"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "toggle"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "saveSidebarCollapsed"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "loadSidebarWidth"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "saveSidebarCollapsed"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "preventDefault"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "max"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "remove"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "saveSidebarWidth"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "fetch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "json"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "switchSidebarTab"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "toggle"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "toggle"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "loadFusionTab"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "loadAnalysisModels"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "loadFusionTab"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "loadFusionStats"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "loadFusionTopChanged"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "loadFusionSearch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "loadFusionStats"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "fetch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "json"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "loadFusionTopChanged"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "fetch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "json"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "map"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeAttr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeAttr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeAttr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeAttr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "join"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "loadFusionEntityDetail"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "analyseEntity"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "loadFusionSearch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "doSearch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "trim"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "parseInt"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "set"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "set"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "fetch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "json"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "map"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeAttr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeAttr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeAttr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeAttr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "join"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "loadFusionEntityDetail"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "analyseEntity"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "doSearch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "loadFusionEntityDetail"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "fetch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "json"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "replace"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "isArray"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "map"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "join"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "isArray"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "map"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "join"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "isArray"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "map"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "join"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "toFixed"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "toISOString"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "replace"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "substring"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "toISOString"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "replace"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "substring"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "closest"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "toggleThinking"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "switchSidebarTab"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "discoverer"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "function"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "querySelector"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "trim"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "_sseUrl"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "indexOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "encodeURIComponent"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setDiscoverProgress"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "hideDiscoverProgress"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "startDiscover"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "trim"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "ensureLanConsent"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "showToast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "stopDiscover"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setDiscoverProgress"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "fetch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "json"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "parse"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "parse"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "handleDiscoverEvent"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "parse"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "hideDiscoverProgress"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "close"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "flushDiscoverEntities"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "hideDiscoverProgress"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "stopDiscover"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "fetch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "close"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "hideDiscoverProgress"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "handleDiscoverEvent"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setDiscoverProgress"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setDiscoverProgress"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addDiscoverEntityToTab"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "maybePlotDiscoverEntity"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setDiscoverProgress"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "hideDiscoverProgress"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setStatus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addDiscoverEntityToTab"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHtml"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHtml"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHtml"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "dispatchEvent"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "escapeHtml"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "replace"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "replace"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "replace"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "replace"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "maybePlotDiscoverEntity"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "flushDiscoverEntities"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "_drawDiscoverGraph"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "initOllamaCheck"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "check"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "fetch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "json"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/estorides.js", "target": "check"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "strength"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "innerHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "keys"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "function"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "function"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "djb2KindColor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "charCodeAt"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "hsl"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "matchMedia"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "clip"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "slice"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "tabActive"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "contains"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "ink"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "getComputedStyle"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "function"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "getPropertyValue"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "trim"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "rgba"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "rgba"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "getAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "colorOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "djb2KindColor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "keyOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "bspline"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "concat"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "max"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "curve"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "hub"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "hub"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "hub"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "max"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "map"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "buildCurves"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "map"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "curve"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "sin"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "cos"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "sin"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "function"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "map"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "curve"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "resize"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "getBoundingClientRect"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "max"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "max"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "round"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "round"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "max"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "min"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "draw"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "radius"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "max"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "max"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "center"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "makeProjector"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "center"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "radius"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "function"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "cos"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "sin"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "cos"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "sin"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "function"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "depthAlpha"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "focusState"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "edgeState"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "keyOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "keyOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "nodeLit"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "some"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "some"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "keyOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "strokeCurve"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "max"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "proj"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "moveTo"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "proj"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "lineTo"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "pointOn"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "min"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "proj"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "draw"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "ink"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "focusState"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "makeProjector"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "setTransform"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "clearRect"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "center"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "radius"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "beginPath"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "arc"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "fill"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "beginPath"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "cos"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "proj"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "sin"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "sin"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "lineTo"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "moveTo"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "stroke"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "edgeState"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "beginPath"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "strokeCurve"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "min"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "depthAlpha"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "colorOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "max"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "stroke"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "filter"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "beginPath"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "strokeCurve"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "colorOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "min"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "stroke"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "slice"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "pointOn"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "depthAlpha"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "beginPath"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "arc"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "fill"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "drawGroups"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "drawNodes"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "drawLabels"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "drawHud"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "drawGroups"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "beginPath"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "arc"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "stroke"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "cos"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "sin"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "cos"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "fillText"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "proj"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "clip"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "measureText"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "depthAlpha"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "fillRect"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "strokeRect"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "fillText"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "nodeRadius"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "return"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "sqrt"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "min"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "screenNodes"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "map"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "proj"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "proj"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "sin"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "nodeRadius"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "drawNodes"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "screenNodes"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "slice"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "sort"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "nodeLit"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "depthAlpha"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "beginPath"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "arc"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "colorOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "fill"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "stroke"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "labelSet"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "drawLabels"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "labelSet"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "cos"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "nodeLit"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "save"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "translate"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "cos"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "sin"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "rotate"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "colorOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "fillText"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "restore"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "slice"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "sort"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "return"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "clip"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "measureText"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "nodeLit"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "depthAlpha"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "fillRect"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "fillText"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "drawHud"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "hitNode"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "hypot"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "max"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "center"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "radius"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "hypot"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "atan2"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "abs"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "cos"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "max"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "hitGroup"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "center"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "radius"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "hypot"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "atan2"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "findIndex"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "proj"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "hypot"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "tipFor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "flowsOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "getBoundingClientRect"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "min"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "min"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "flowsOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "rank"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "keys"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "map"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "sort"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "return"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "rank"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "rank"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "inspectInGraph"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "encodeURIComponent"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "querySelector"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "click"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "querySelector"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "click"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "nodeButton"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "colorOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "focusNode"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "groupButton"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "focusGroup"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "listInto"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "slice"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "renderPanel"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "colorOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "focusGroup"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "inspectInGraph"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "map"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "sort"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "map"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "sort"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "listInto"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "nodeButton"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "listInto"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "nodeButton"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "flowsOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "listInto"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "function"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "groupButton"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "listInto"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "function"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "groupButton"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "map"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "filter"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "sort"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "listInto"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "nodeButton"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "keys"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "map"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "sort"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "return"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "slice"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "split"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "map"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "clip"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "focusGroup"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "renderLegend"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "focusGroup"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "keyOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "keys"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "sort"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "return"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "ink"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "djb2KindColor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "sync"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "focusNode"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "aimAt"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "sync"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "focusGroup"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "aimAt"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "sync"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "facing"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "atan2"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "max"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "aimAt"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "facing"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "now"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "setView"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "resize"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "sync"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "setColor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "sync"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "setDir"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "sync"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "setBeta"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "max"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "toFixed"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "buildCurves"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "sync"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "sync"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "toFixed"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "renderPanel"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "renderLegend"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "writeHash"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "draw"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "writeHash"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "set"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "set"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "set"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "set"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "set"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "set"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "set"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "replaceState"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "readHash"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "slice"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "get"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "get"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "parseFloat"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "max"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "get"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "get"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "get"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "get"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "get"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "ingest"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "map"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "map"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "apply"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "map"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "sort"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "return"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "filter"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "buildCurves"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "readHash"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "facing"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "resize"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "sync"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "refresh"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "ingest"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "fetch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "json"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "ingest"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "data"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "frame"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "min"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "requestAnimationFrame"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "min"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "max"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "min"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "max"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "pow"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "atan2"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "cos"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "draw"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "requestAnimationFrame"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "init"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "getContext"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "setPointerCapture"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "getBoundingClientRect"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "max"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "draw"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "hitNode"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "hitGroup"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "draw"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "toggle"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "tipFor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "remove"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "getBoundingClientRect"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "hitNode"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "focusNode"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "hitGroup"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "focusGroup"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "sync"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "draw"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "preventDefault"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "max"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "draw"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "sync"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "setView"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "setColor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "setDir"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "setBeta"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "sync"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "sync"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "setBeta"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "toDataURL"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "click"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "toggle"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "contains"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "search"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "trim"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "toLowerCase"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "remove"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "map"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "filter"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "toLowerCase"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "indexOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "sort"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "return"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "indexOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "toLowerCase"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "indexOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "slice"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "colorOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "focusNode"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "remove"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "blur"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "append"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "toggle"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "search"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "search"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "focusNode"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "remove"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "blur"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "search"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "blur"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "refresh"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "resize"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "resize"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "observe"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "freeze"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "refresh"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "requestAnimationFrame"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "time"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "setView"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "setView"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "sync"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "setColor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "sync"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "setBeta"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "preventDefault"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "focus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "stopImmediatePropagation"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_bundle.js", "target": "init"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "module"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "values"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "graph"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "anywhere"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "constant"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "function"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "function"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "function"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "slice"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "function"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "escapeHTML"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "rgba"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "rgba"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "look"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "overlay"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "reducedMotion"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "matchMedia"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "toast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "setVisible"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "clearTimeout"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "setTimeout"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "setVisible"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "fail"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "setVisible"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "settings"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "shape"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "adaptLocal"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "keys"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "slice"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "sort"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "return"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "localeCompare"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "safeColor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "round"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "max"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "keys"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "safeColor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "currentRaw"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "settings"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "map"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "max"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "settings"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "rebuildRaw"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "map"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "keys"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "max"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "filter"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "map"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "adaptLocal"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "indexRaw"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "indexRaw"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "settings"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "filter"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "slice"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "sort"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "return"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "slice"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "lkey"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "colorOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "safeColor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "dimmed"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "indexOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "hiddenKind"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "visiblePayload"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "filter"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "filter"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "filter"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "filter"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "filter"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "computeHighlight"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "tip"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "esc"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "esc"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "esc"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "esc"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "esc"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "replace"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forces"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "refreshFamList"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "keys"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "sort"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "return"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "famAnchor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "indexOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "max"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "max"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "cos"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "sin"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "famOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "clusterForce"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "force"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "famOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "famAnchor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "function"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "ringOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "indexOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "return"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "radialForce"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "force"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "ringOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "hypot"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "function"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "collideForce"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "force"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "radius"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "radius"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "abs"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "abs"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "hypot"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "function"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "radius"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "settings"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "sqrt"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "linkStrengthFn"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "function"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "famOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "famOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "setEngineButtons"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "show3DChrome"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "toggle"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "toggle"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "setVisible"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "toggle"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "setVisible"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "resumeAnimation"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "pauseAnimation"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "resize3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "mount3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "toast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "function"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "mount3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "function"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "toast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "init"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "marker"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "remove"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "settings"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "refreshFamList"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "visiblePayload"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "graphData"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "nodeId"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "nodeVal"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "nodeRelSize"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "nodeVisibility"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "nodeLabel"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "enablePointerInteraction"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "nodeColor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "dimmed"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "colorOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "linkColor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "lkey"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "rgba"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "linkWidth"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "lkey"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "linkDirectionalParticles"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "lkey"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "backgroundColor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "showNavInfo"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "cooldownTicks"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "controls"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "controls"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "d3Force"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "strength"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "d3Force"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "distance"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "strength"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "d3Force"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "d3Force"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "d3Force"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "applyLayout3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "resize3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "startOverlayLoop"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "hud"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "setTimeout"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "zoomToFit"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "remove"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "fail"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "hidden"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "glyph"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "startOverlayLoop"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "requestAnimationFrame"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "overlayTick"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "getContext"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "clearRect"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "paintOverlay3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "requestAnimationFrame"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "convexHullPts"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "slice"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "sort"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "return"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "cross"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "return"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "pop"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "pop"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "pop"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "pop"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "concat"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "inflateHull"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "map"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "hypot"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "traceSmooth"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "moveTo"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "lineTo"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "closePath"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "function"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "mid"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "moveTo"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "mid"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "quadraticCurveTo"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "closePath"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "beginPath"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "moveTo"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "arcTo"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "arcTo"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "arcTo"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "arcTo"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "closePath"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "glyphPath"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "beginPath"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "cos"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "sin"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "moveTo"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "lineTo"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "closePath"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "moveTo"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "lineTo"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "lineTo"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "lineTo"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "closePath"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "moveTo"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "lineTo"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "lineTo"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "closePath"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "tierColorOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "drawGlyph3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "colorOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "dimmed"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "save"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "glyphPath"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "rgba"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "fill"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "stroke"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "rgba"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "max"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "fillRect"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "fillRect"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "fillRect"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "beginPath"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "arc"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "tierColorOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "stroke"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "fill"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "rgba"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "stroke"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "glyphPath"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "rgba"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "fill"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "restore"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "save"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "beginPath"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "arc"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "stroke"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "restore"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "drawLabel3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "short"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "save"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "max"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "measureText"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "rgba"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "fill"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "stroke"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "colorOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "fillText"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "restore"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "paintOverlay3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "min"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "round"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "round"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "round"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "getContext"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "setTransform"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "clearRect"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "graphData"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "camera"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "tan"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "settings"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "graph2ScreenCoords"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "hypot"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "max"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "sqrt"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "max"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "sort"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "keys"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "inflateHull"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "colorOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "save"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "traceSmooth"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "fill"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "setLineDash"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "traceSmooth"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "stroke"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "setLineDash"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "reduce"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "fillText"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "restore"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "drawGlyph3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "filter"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "sort"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "return"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "slice"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "keys"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "drawLabel3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "entirely"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "stageLocal"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "getBoundingClientRect"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "hitNode3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "max"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "hypot"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "segDist"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "max"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "hypot"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "hitEdge3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "graphData"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "segDist"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "hoverAt3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "hitNode3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "hitEdge3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "computeHighlight"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "refresh3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "bindStagePointer"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "stageLocal"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "hitNode3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "onSelect3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "hitEdge3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "onEdge3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "clearSelection"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "preventDefault"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "expandSelected"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "stageLocal"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "hitNode3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "preventDefault"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "stopPropagation"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "showContextMenu"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "stageLocal"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "requestAnimationFrame"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "hoverAt3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "refresh3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "nodeColor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "linkColor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "linkWidth"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "linkDirectionalParticles"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "reload3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "mount3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "graphData"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "hud"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "resize3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "width"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "height"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "applyLayout3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "settings"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "dagMode"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "dagLevelDistance"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "d3Force"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "strength"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "d3Force"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "strength"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "d3ReheatSimulation"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "hud"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "onSelect3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "markFamilies"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "computeHighlight"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "reload3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "refresh3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "hud"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "writeHash"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "selectNode"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "showContextMenu"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "expandSelected"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "toast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "resolverTypeFor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "toast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "expandNode"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "toast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "focusSelected"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "toast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "settings"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "centerAt"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "zoom"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "copyDeepLink"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "done"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "toast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "writeText"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "toast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "toast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "toast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "reheat"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "d3ReheatSimulation"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "toast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "toggleBridges"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "reload3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "applyFilters"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "toast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "screen"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "snapshot3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "toast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "toast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "getContext"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "fillRect"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "drawImage"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "drawImage"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "toDataURL"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "click"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "toast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "toggleOrbit"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "controls"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "controls"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "toast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "onEdge3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "showBridgeTooltip"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "toast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "clearSelection"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "markFamilies"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "syncIsolateBtn"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "reload3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "refresh3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "hud"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "writeHash"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "focusFamily"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "clearSelection"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "renderLegend"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "keys"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "markFamilies"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "refresh3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "hud"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "zoomToFit"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "markFamilies"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "hud"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "stat"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "graphData"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "short"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "renderLegend"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "keys"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "sort"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "toggle"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "applyFilters"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "refreshFamList"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "safeColor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "safeColor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "focusFamily"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "applyFilters"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "reload3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "keys"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "filter"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "filter"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "hud"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "runSearch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "trim"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "toLowerCase"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "settings"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "remove"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "applyFilters"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "refresh3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "toLowerCase"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "toLowerCase"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "min"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "sort"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "return"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "slice"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "refresh3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "colorOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "pickHit"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "remove"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "onSelect3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "selectNode"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "selectNode"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "to3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "setEngineButtons"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "show3DChrome"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "renderLegend"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "mount3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "writeHash"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "to2D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "setEngineButtons"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "show3DChrome"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "applyFilters"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "writeHash"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "readHash"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "slice"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "get"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "applyLayout3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "get"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "onSelect3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "selectNode"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "writeHash"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "set"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "set"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "replaceState"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "syncIsolateBtn"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "wireToolbar"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "to3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "to2D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "toast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "applyLayout3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "writeHash"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "toast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "onSelect3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "toggle"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "after"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "toggle"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "toggle"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "toggle"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "refresh3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "cooldownTicks"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "cooldownTicks"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "d3ReheatSimulation"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "toast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "zoomToFit"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "toggleOrbit"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "toggleBridges"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "snapshot3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "querySelector"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "toast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "toDataURL"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "click"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "toast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "createObjectURL"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "click"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "setTimeout"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "revokeObjectURL"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "syncIsolateBtn"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "reload3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "applyFilters"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "preventDefault"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "setAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "preventDefault"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "pickHit"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "runSearch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "blur"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "setTimeout"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "remove"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "closest"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "pickHit"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "querySelector"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "preventDefault"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "focus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "clearSelection"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "hideTooltip"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "preventDefault"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "expandSelected"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "preventDefault"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "pop"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "onSelect3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "click"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "click"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "preventDefault"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "focusSelected"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "click"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "reheat"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "click"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "click"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "expandSelected"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "copyDeepLink"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "preventDefault"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "click"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "click"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "click"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "setInterval"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "setVisible"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "rebuildRaw"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "renderLegend"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "reload3D"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "applyFilters"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "readHash"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "wireToolbar"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "bindStagePointer"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "setEngineButtons"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "function"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/graph_force.js", "target": "function"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "function"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "authHeaders"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "querySelector"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "getAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "apiFetch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "assign"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "fetch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "json"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "json"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "function"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "getCheckedTags"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "from"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "map"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "setCheckedTags"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "has"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "readForm"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "trim"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "trim"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "trim"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "trim"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "trim"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "getCheckedTags"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "getCheckedTags"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "trim"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "parse"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "parse"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "parse"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "parseInt"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "trim"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "trim"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "writeForm"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "reset"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "stringify"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "keys"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "stringify"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "keys"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "stringify"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "keys"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "stringify"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "setCheckedTags"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "setCheckedTags"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "updateYamlPreview"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "updateYamlPreview"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "readForm"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "stringify"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "renderList"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "toLowerCase"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "toLowerCase"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "indexOf"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "push"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "keys"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "sort"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "escHtml"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "escAttr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "escHtml"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "escHtml"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "escHtml"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "replace"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "return"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "escAttr"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "replace"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "toast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "setTimeout"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "removeChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "loadSources"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "apiFetch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "renderList"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "clearEditor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "toast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "clearEditor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "writeForm"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "remove"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "selectSource"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "filter"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "writeForm"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "remove"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "querySelector"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "add"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "saveSource"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "toast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "readForm"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "toast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "toast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "encodeURIComponent"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "apiFetch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "toast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "loadSources"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "selectSource"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "toast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "deleteSource"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "createElement"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "escHtml"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "appendChild"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "remove"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "apiFetch"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "then"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "toast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "clearEditor"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "loadSources"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "toast"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "remove"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "remove"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "newSource"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "writeForm"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "setCheckedTags"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "focus"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "remove"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "preventDefault"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "saveSource"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "renderList"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "click"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "closest"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "selectSource"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "updateYamlPreview"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "querySelectorAll"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "forEach"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "addEventListener"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "preventDefault"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "getAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "getAttribute"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "getElementById"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "updateYamlPreview"}, {"confidence": "EXTRACTED", "relation": "calls", "source": "static/js/source_manager.js", "target": "loadSources"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/conftest.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/conftest.py", "target": "sys"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/conftest.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/conftest.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/conftest.py", "target": "hypothesis"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_change_detection_properties.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_change_detection_properties.py", "target": "re"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_change_detection_properties.py", "target": "pytest"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_change_detection_properties.py", "target": "hypothesis"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_change_detection_properties.py", "target": "hypothesis"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_change_detection_properties.py", "target": "estorides_core.change_detection"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_csp_safe_styles_properties.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_csp_safe_styles_properties.py", "target": "re"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_csp_safe_styles_properties.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_csp_safe_styles_properties.py", "target": "hypothesis"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_csp_safe_styles_properties.py", "target": "hypothesis"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_csp_safe_styles_properties.py", "target": "estorides_core.web_security"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_graph_bundle_properties.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_graph_bundle_properties.py", "target": "json"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_graph_bundle_properties.py", "target": "math"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_graph_bundle_properties.py", "target": "pytest"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_graph_bundle_properties.py", "target": "hypothesis"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_graph_bundle_properties.py", "target": "hypothesis"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_graph_bundle_properties.py", "target": "estorides_core.graph_bundle"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_hypothesis_engine_properties.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_hypothesis_engine_properties.py", "target": "hypothesis"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_hypothesis_engine_properties.py", "target": "hypothesis"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_hypothesis_engine_properties.py", "target": "estorides_core.hypothesis_engine"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_hypothesis_engine_properties.py", "target": "re"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_observation_models_properties.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_observation_models_properties.py", "target": "hypothesis"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_observation_models_properties.py", "target": "hypothesis"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_observation_models_properties.py", "target": "estorides_core.observation_models"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_parsers_properties.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_parsers_properties.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_parsers_properties.py", "target": "hypothesis"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_parsers_properties.py", "target": "hypothesis"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_parsers_properties.py", "target": "estorides_core.parsers"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_recon_fusion_properties.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_recon_fusion_properties.py", "target": "unittest.mock"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_recon_fusion_properties.py", "target": "hypothesis"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_recon_fusion_properties.py", "target": "hypothesis"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_recon_fusion_properties.py", "target": "estorides_core.recon_fusion"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_recon_fusion_properties.py", "target": "pytest"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_reliability_scoring_properties.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_reliability_scoring_properties.py", "target": "hypothesis"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_reliability_scoring_properties.py", "target": "hypothesis"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_reliability_scoring_properties.py", "target": "estorides_core.reliability_scoring"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_reliability_scoring_properties.py", "target": "estorides_core.reliability_scoring"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_reliability_scoring_properties.py", "target": "estorides_core.reliability_scoring"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_reliability_scoring_properties.py", "target": "estorides_core.reliability_scoring"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_reliability_scoring_properties.py", "target": "estorides_core.reliability_scoring"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_reliability_scoring_properties.py", "target": "estorides_core.reliability_scoring"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_reliability_scoring_properties.py", "target": "estorides_core.reliability_scoring"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_search_telemetry_properties.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_search_telemetry_properties.py", "target": "hypothesis"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_search_telemetry_properties.py", "target": "hypothesis"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_search_telemetry_properties.py", "target": "estorides_core.search_telemetry"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_source_health_monitoring_properties.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_source_health_monitoring_properties.py", "target": "hypothesis"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_source_health_monitoring_properties.py", "target": "hypothesis"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_source_health_monitoring_properties.py", "target": "estorides_core.source_health_monitoring"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_system_app_sources_properties.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_system_app_sources_properties.py", "target": "hypothesis"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_system_app_sources_properties.py", "target": "hypothesis"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_system_app_sources_properties.py", "target": "estorides_core.system_app_sources"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_system_app_sources_properties.py", "target": "estorides_core.parsers"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_system_app_sources_properties.py", "target": "tempfile"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_system_app_sources_properties.py", "target": "pytest"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_system_app_sources_properties.py", "target": "estorides_core.tool_runner"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_system_app_sources_properties.py", "target": "os"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_target_management_properties.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_target_management_properties.py", "target": "re"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_target_management_properties.py", "target": "hypothesis"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_target_management_properties.py", "target": "estorides_core.target_management"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_tool_runner_properties.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_tool_runner_properties.py", "target": "pytest"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_tool_runner_properties.py", "target": "hypothesis"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_tool_runner_properties.py", "target": "hypothesis"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/properties/test_tool_runner_properties.py", "target": "estorides_core.tool_runner"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_active_recon.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_active_recon.py", "target": "estorides_core.active_recon"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_active_recon.py", "target": "estorides_core.tool_runner"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_async_client.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_async_client.py", "target": "asyncio"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_async_client.py", "target": "sys"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_async_client.py", "target": "estorides_core.async_client"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_async_client.py", "target": "estorides_core.config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_audit_log.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_audit_log.py", "target": "os"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_audit_log.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_audit_log.py", "target": "pytest"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_audit_log.py", "target": "estorides_core.audit"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_auth_gate.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_auth_gate.py", "target": "pytest"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_auth_gate.py", "target": "flask"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_auth_gate.py", "target": "estorides_core.web_security"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_auth_gate.py", "target": "re"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_auth_gate.py", "target": "estorides_core.web_security"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_auth_gate.py", "target": "flask"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_case_crypto.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_case_crypto.py", "target": "importlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_case_crypto.py", "target": "estorides_core.case_crypto"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_case_crypto.py", "target": "estorides_core.cases"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_case_crypto.py", "target": "cryptography.fernet"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_case_crypto.py", "target": "cryptography.fernet"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_case_crypto.py", "target": "pytest"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_case_crypto.py", "target": "pytest"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_central_config.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_central_config.py", "target": "inspect"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_central_config.py", "target": "estorides_core"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_central_config.py", "target": "estorides_core"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_central_config.py", "target": "estorides_core.orchestrator"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_central_config.py", "target": "importlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_central_config.py", "target": "estorides_core.tool_install"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_change_detection.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_change_detection.py", "target": "pytest"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_change_detection.py", "target": "estorides_core.change_detection"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_change_detection.py", "target": "estorides_core.reliability_scoring"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_cli_watch.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_cli_watch.py", "target": "argparse"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_cli_watch.py", "target": "pytest"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_cli_watch.py", "target": "estorides_cli"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_cli_watch.py", "target": "estorides_core.monitoring"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_cli_watch.py", "target": "estorides_core.monitoring"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_cli_watch.py", "target": "estorides_cli"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_cli_watch.py", "target": "estorides_core.monitoring"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_cloud_asset_discovery.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_cloud_asset_discovery.py", "target": "estorides_core.cloud_asset_discovery"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_cloud_asset_discovery.py", "target": "estorides_core.cloud_asset_discovery"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_code_exposure.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_code_exposure.py", "target": "estorides_core.code_exposure"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_config_env.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_config_env.py", "target": "os"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_config_env.py", "target": "subprocess"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_config_env.py", "target": "sys"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_config_env.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_config_env.py", "target": "estorides_core.config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_config_env.py", "target": "estorides_core.config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_config_env.py", "target": "estorides_core.config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_config_env.py", "target": "estorides_core.config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_config_env.py", "target": "estorides_core.config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_config_env.py", "target": "estorides_core.config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_csp_safe_styles.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_csp_safe_styles.py", "target": "re"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_csp_safe_styles.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_csp_safe_styles.py", "target": "pytest"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_csp_safe_styles.py", "target": "jinja2"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_csp_safe_styles.py", "target": "estorides_core.search_telemetry"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_csp_safe_styles.py", "target": "estorides_core.web_security"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_csp_safe_styles.py", "target": "estorides_core.web_security"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_csp_safe_styles.py", "target": "flask"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_encrypted_export.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_encrypted_export.py", "target": "subprocess"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_encrypted_export.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_encrypted_export.py", "target": "unittest.mock"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_encrypted_export.py", "target": "pytest"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_encrypted_export.py", "target": "estorides_core.entity_extraction"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_encrypted_export.py", "target": "estorides_core.knowledge_graph"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_encrypted_export.py", "target": "estorides_export.encryption"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_encrypted_export.py", "target": "estorides_export.encryption"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_encrypted_export.py", "target": "estorides_export.encryption"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_entity_extraction.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_entity_extraction.py", "target": "estorides_core.entity_extraction"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_entity_resolution.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_entity_resolution.py", "target": "tempfile"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_entity_resolution.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_entity_resolution.py", "target": "estorides_core.entity_extraction"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_entity_resolution.py", "target": "estorides_core.entity_resolution"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_entity_resolution.py", "target": "estorides_core.entity_store"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_entity_resolution.py", "target": "estorides_core.transliteration"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_envutil.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_envutil.py", "target": "estorides_core.envutil"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_event_bus.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_event_bus.py", "target": "pytest"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_event_bus.py", "target": "estorides_core.event_bus"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_event_bus.py", "target": "estorides_core.event_bus"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_event_bus.py", "target": "estorides_core.event_bus"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_event_bus.py", "target": "estorides_core.event_bus"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_event_bus.py", "target": "estorides_core.event_bus"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_fusion_analytics.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_fusion_analytics.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_fusion_analytics.py", "target": "collections.abc"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_fusion_analytics.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_fusion_analytics.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_fusion_analytics.py", "target": "pytest"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_fusion_analytics.py", "target": "estorides_core.fusion_analytics"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_fusion_analytics.py", "target": "estorides_core.fusion_store"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_graph_bundle.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_graph_bundle.py", "target": "json"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_graph_bundle.py", "target": "math"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_graph_bundle.py", "target": "pytest"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_graph_bundle.py", "target": "estorides_core.graph_bundle"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_graph_bundle.py", "target": "estorides_core.graph_bundle"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_graph_bundle.py", "target": "estorides_core.graph_bundle"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_graph_bundle.py", "target": "estorides_core"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_graph_bundle.py", "target": "estorides_core.graph_force"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_graph_bundle.py", "target": "estorides_core.graph_force"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_graph_bundle.py", "target": "estorides_core"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_graph_bundle.py", "target": "estorides_core.graph_bundle"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_graph_bundle.py", "target": "estorides_core.graph_bundle"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_graph_bundle.py", "target": "estorides_core.graph_bundle"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_graph_bundle.py", "target": "estorides_core.graph_force"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_graph_bundle.py", "target": "estorides_core.graph_bundle"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_graph_bundle.py", "target": "estorides_core.graph_bundle"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_graph_bundle.py", "target": "estorides_core.graph_bundle"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_graph_bundle.py", "target": "estorides_core.graph_bundle"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_graph_bundle_assets.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_graph_bundle_assets.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_graph_bundle_assets.py", "target": "pytest"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_graph_force3d.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_graph_force3d.py", "target": "json"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_graph_force3d.py", "target": "estorides_core.graph_force"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_graph_force3d.py", "target": "re"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_graph_rag_search.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_graph_rag_search.py", "target": "json"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_graph_rag_search.py", "target": "networkx"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_graph_rag_search.py", "target": "pytest"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_graph_rag_search.py", "target": "estorides_core.graph_rag_search"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_graph_rag_search.py", "target": "estorides_web"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_hardening.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_hardening.py", "target": "subprocess"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_hardening.py", "target": "sys"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_hardening.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_hardening.py", "target": "pytest"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_hardening.py", "target": "flask"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_hardening.py", "target": "estorides_core.cases"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_hardening.py", "target": "estorides_core.web_security"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_hardening.py", "target": "estorides_export.report"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_hypothesis_engine.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_hypothesis_engine.py", "target": "pytest"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_hypothesis_engine.py", "target": "estorides_core.hypothesis_engine"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_hypothesis_engine.py", "target": "estorides_core.reliability_scoring"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_ids.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_ids.py", "target": "hashlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_ids.py", "target": "estorides_core.ids"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_job_registry.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_job_registry.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_job_registry.py", "target": "pytest"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_job_registry.py", "target": "estorides_core.job_registry"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_keyless_sources.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_keyless_sources.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_keyless_sources.py", "target": "estorides_core.orchestrator"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_keyless_sources.py", "target": "estorides_core.parsers"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_keyless_sources.py", "target": "estorides_core.parsers"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_keyless_sources.py", "target": "estorides_core.parsers"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_lan_bundle.py", "target": "json"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_lan_bundle.py", "target": "estorides_core"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_lan_bundle.py", "target": "estorides_core.lan_inventory"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_lan_bundle.py", "target": "warnings"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_lan_bundle.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_lan_inventory.py", "target": "estorides_core"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_lan_policy.py", "target": "estorides_core"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_lan_runner.py", "target": "estorides_core"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_lan_runner.py", "target": "estorides_core.tool_runner"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_lan_scope.py", "target": "estorides_core"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_lan_web.py", "target": "estorides_core"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_map_basemap.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_map_basemap.py", "target": "re"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_map_basemap.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_map_basemap.py", "target": "estorides_core.web_security"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_monitoring.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_monitoring.py", "target": "tempfile"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_monitoring.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_monitoring.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_monitoring.py", "target": "pytest"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_monitoring.py", "target": "estorides_core.alerter"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_monitoring.py", "target": "estorides_core.config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_monitoring.py", "target": "estorides_core.monitoring"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_monitoring.py", "target": "estorides_core.source_loader"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_obs_fts.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_obs_fts.py", "target": "importlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_obs_fts.py", "target": "estorides_core.cases"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_obs_fts.py", "target": "pytest"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_observation_models.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_observation_models.py", "target": "pytest"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_observation_models.py", "target": "pydantic"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_observation_models.py", "target": "estorides_core.observation_models"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_openapi.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_openapi.py", "target": "estorides_web"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_ops_observability.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_ops_observability.py", "target": "json"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_ops_observability.py", "target": "threading"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_ops_observability.py", "target": "estorides_core.ops_observability"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_ops_observability.py", "target": "estorides_core.ops_observability"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_ops_observability.py", "target": "estorides_core.ops_observability"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_ops_observability.py", "target": "estorides_core.ops_observability"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_ops_observability.py", "target": "estorides_core.ops_observability"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_ops_observability.py", "target": "estorides_core.ops_observability"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_opsec_contact.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_opsec_contact.py", "target": "estorides_core.config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_opsec_contact.py", "target": "estorides_core.orchestrator"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_opsec_contact.py", "target": "estorides_core.source_loader"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_orchestrator_fanout.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_orchestrator_fanout.py", "target": "asyncio"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_orchestrator_fanout.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_orchestrator_fanout.py", "target": "estorides_core"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_orchestrator_fanout.py", "target": "inspect"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_orchestrator_fanout.py", "target": "inspect"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_pagination.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_pagination.py", "target": "estorides_core.pagination"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_parsers.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_parsers.py", "target": "pytest"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_parsers.py", "target": "estorides_core"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_pdns_monitor.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_pdns_monitor.py", "target": "estorides_core.pdns_monitor"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_pdns_monitor.py", "target": "estorides_core.pdns_monitor"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_people_intel.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_people_intel.py", "target": "pytest"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_people_intel.py", "target": "estorides_core.people_intel"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_probabilistic_fusion.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_probabilistic_fusion.py", "target": "tempfile"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_probabilistic_fusion.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_probabilistic_fusion.py", "target": "estorides_core.fusion_store"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_probabilistic_fusion.py", "target": "estorides_core.fusion_store"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_query_intent.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_query_intent.py", "target": "estorides_core.entity_extraction"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_recon_fusion.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_recon_fusion.py", "target": "pytest"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_recon_fusion.py", "target": "estorides_core.config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_recon_fusion.py", "target": "estorides_core.recon_fusion"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_recon_report.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_recon_report.py", "target": "re"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_recon_report.py", "target": "estorides_export.recon_report"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_recon_report.py", "target": "estorides_export.recon_report"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_reliability_scoring.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_reliability_scoring.py", "target": "math"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_reliability_scoring.py", "target": "itertools"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_reliability_scoring.py", "target": "pytest"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_reliability_scoring.py", "target": "estorides_core.reliability_scoring"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_retry_policy.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_retry_policy.py", "target": "estorides_core.config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_retry_policy.py", "target": "estorides_core"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_retry_policy.py", "target": "estorides_core.config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_scope.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_scope.py", "target": "estorides_core.scope"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_search_telemetry.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_search_telemetry.py", "target": "re"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_search_telemetry.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_search_telemetry.py", "target": "pytest"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_search_telemetry.py", "target": "jinja2"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_search_telemetry.py", "target": "estorides_core.search_telemetry"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "json"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "re"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "socket"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "unittest.mock"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "pytest"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "flask"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "estorides_core.ssrf_guard"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "estorides_core.web_security"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "shutil"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "tempfile"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "flask"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "shutil"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "tempfile"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "flask"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "flask"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "inspect"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "estorides_core"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "inspect"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "estorides_core"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "estorides_core.alerter"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "estorides_core.alerter"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "estorides_core.alerter"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "estorides_core.alerter"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "unittest.mock"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "estorides_core"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "estorides_core.alerter"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "estorides_core.alerter"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "urllib.request"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "estorides_core"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "estorides_core.ssrf_guard"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "flask"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "flask"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "flask"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "flask"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "flask"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_security_remediation.py", "target": "flask"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_socmint.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_socmint.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_socmint.py", "target": "pytest"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_socmint.py", "target": "estorides_core.entity_extraction"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_socmint.py", "target": "estorides_core.parsers"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_socmint.py", "target": "estorides_core.socmint"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_socmint.py", "target": "estorides_core.config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_socmint.py", "target": "estorides_core.source_loader"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_socmint.py", "target": "estorides_core.socmint"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_socmint.py", "target": "estorides_core.socmint"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_source_health_monitoring.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_source_health_monitoring.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_source_health_monitoring.py", "target": "pytest"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_source_health_monitoring.py", "target": "estorides_core.source_health_monitoring"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_source_loader.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_source_loader.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_source_loader.py", "target": "estorides_core.source_loader"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_source_routing.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_source_routing.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_source_routing.py", "target": "yaml"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_source_routing.py", "target": "estorides_core.orchestrator"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_source_routing.py", "target": "estorides_core.orchestrator"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_sqlite_store.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_sqlite_store.py", "target": "dataclasses"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_sqlite_store.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_sqlite_store.py", "target": "pytest"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_sqlite_store.py", "target": "estorides_core.sqlite_store"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_structured_extraction.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_structured_extraction.py", "target": "asyncio"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_structured_extraction.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_structured_extraction.py", "target": "estorides_core.config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_structured_extraction.py", "target": "estorides_core.entity_extraction"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_structured_extraction.py", "target": "estorides_core.pivot_engine"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_supply_chain.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_supply_chain.py", "target": "estorides_core.supply_chain"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_system_app_sources.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_system_app_sources.py", "target": "json"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_system_app_sources.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_system_app_sources.py", "target": "typing"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_system_app_sources.py", "target": "pytest"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_system_app_sources.py", "target": "yaml"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_system_app_sources.py", "target": "estorides_core.source_loader"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_system_app_sources.py", "target": "estorides_core.system_app_sources"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_system_app_sources.py", "target": "estorides_core.tool_runner"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_system_app_sources.py", "target": "estorides_core.system_app_sources"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_system_app_sources.py", "target": "estorides_core.system_app_sources"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_system_app_sources.py", "target": "estorides_core.system_app_sources"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_system_app_sources.py", "target": "estorides_core.system_app_sources"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_system_app_sources.py", "target": "estorides_core.system_app_sources"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_system_app_sources.py", "target": "estorides_core.orchestrator"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_system_app_sources.py", "target": "estorides_core.orchestrator"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_system_app_sources.py", "target": "estorides_core.config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_system_app_sources.py", "target": "estorides_core.system_app_sources"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_system_app_sources.py", "target": "asyncio"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_system_app_sources.py", "target": "time"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_system_app_sources.py", "target": "estorides_core.system_app_sources"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_system_app_sources.py", "target": "estorides_core.orchestrator"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_system_app_sources.py", "target": "estorides_core.source_loader"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_target_management.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_target_management.py", "target": "pytest"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_target_management.py", "target": "estorides_core.target_management"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_target_management.py", "target": "estorides_core.target_management"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_target_scoring.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_target_scoring.py", "target": "estorides_core.target_scoring"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_tech_fingerprint.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_tech_fingerprint.py", "target": "estorides_core.tech_fingerprint"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_tool_doctor.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_tool_doctor.py", "target": "estorides_core.tool_install"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_tool_doctor.py", "target": "estorides_core.tool_install"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_tool_doctor.py", "target": "estorides_core.tool_install"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_tool_doctor.py", "target": "estorides_core.tool_install"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_tool_install.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_tool_install.py", "target": "unittest.mock"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_tool_install.py", "target": "pytest"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_tool_install.py", "target": "estorides_core.tool_install"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_tool_runner.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_tool_runner.py", "target": "pytest"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_tool_runner.py", "target": "estorides_core.config"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_tool_runner.py", "target": "estorides_core.tool_runner"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_tool_runner.py", "target": "estorides_core.validation"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_tool_runner.py", "target": "unittest.mock"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_tool_runner.py", "target": "unittest.mock"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_transforms.py", "target": "estorides_core.transforms"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_transforms.py", "target": "estorides_core.intel_resolver"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_transforms.py", "target": "estorides_core.transforms"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_transforms.py", "target": "estorides_core.transforms"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_transforms.py", "target": "estorides_core.transforms"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_transforms.py", "target": "estorides_core.transforms"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_transforms.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_transforms.py", "target": "yaml"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_transforms.py", "target": "estorides_core.transforms"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_ui_professional.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_ui_professional.py", "target": "json"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_ui_professional.py", "target": "re"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_ui_professional.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_ui_professional.py", "target": "pytest"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_ui_professional.py", "target": "jinja2"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_ui_professional.py", "target": "estorides_core.recon_fusion"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_ui_professional.py", "target": "estorides_core.search_telemetry"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_ui_visibility.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_ui_visibility.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_vuln_correlation.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_vuln_correlation.py", "target": "estorides_core.vuln_correlation"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_web_helpers.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_web_helpers.py", "target": "flask"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_web_helpers.py", "target": "estorides_core.web_security"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_web_helpers.py", "target": "estorides_web"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_web_tools_blueprint.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_web_tools_blueprint.py", "target": "estorides_web"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_web_tools_blueprint.py", "target": "estorides_web"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_web_tools_blueprint.py", "target": "estorides_web"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tests/test_web_tools_blueprint.py", "target": "estorides_web"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tools/split_sources.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tools/split_sources.py", "target": "sys"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tools/split_sources.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tools/split_sources.py", "target": "yaml"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tools/sync_docs.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tools/sync_docs.py", "target": "argparse"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tools/sync_docs.py", "target": "json"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tools/sync_docs.py", "target": "sys"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tools/sync_docs.py", "target": "pathlib"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "tools/sync_docs.py", "target": "estorides_web"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "web.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "web.py", "target": "warnings"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "web.py", "target": "wsgi"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "wsgi.py", "target": "__future__"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "wsgi.py", "target": "logging"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "wsgi.py", "target": "os"}, {"confidence": "EXTRACTED", "relation": "imports", "source": "wsgi.py", "target": "estorides_web"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "app.py", "target": "wsgi.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_cli.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_cli.py", "target": "estorides_core/knowledge_graph.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_cli.py", "target": "estorides_core/orchestrator.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_cli.py", "target": "estorides_core/validation.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_cli.py", "target": "estorides_export/__init__.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_cli.py", "target": "estorides_core/monitoring.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_cli.py", "target": "estorides_core/cases.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_cli.py", "target": "estorides_core/discoverer.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_cli.py", "target": "estorides_core/scope.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_cli.py", "target": "estorides_core/cases.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_cli.py", "target": "estorides_export/report.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_cli.py", "target": "estorides_core/cases.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_cli.py", "target": "estorides_core/fusion_store.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_cli.py", "target": "estorides_core/entity_extraction.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_cli.py", "target": "estorides_core/monitoring.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_cli.py", "target": "estorides_core/monitoring.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_cli.py", "target": "estorides_core/monitoring.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_cli.py", "target": "estorides_core/monitoring.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_cli.py", "target": "estorides_core/monitoring.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_cli.py", "target": "estorides_core/monitoring.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_cli.py", "target": "estorides_core/alerter.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_cli.py", "target": "estorides_core/alerter.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_cli.py", "target": "estorides_core/monitoring.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_cli.py", "target": "estorides_core/monitoring.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_cli.py", "target": "estorides_core/monitoring.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_cli.py", "target": "estorides_web.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_cli.py", "target": "estorides_core/entity_extraction.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/__init__.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/active_recon.py", "target": "estorides_core/tool_runner.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/alerter.py", "target": "estorides_core/ssrf_guard.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/async_client.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/async_client.py", "target": "estorides_core/ssrf_guard.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/async_client.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/audit.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/cases.py", "target": "estorides_core/case_crypto.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/cases.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/cases.py", "target": "estorides_core/sqlite_store.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/change_detection.py", "target": "estorides_core/ids.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/change_detection.py", "target": "estorides_core/reliability_scoring.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/discoverer.py", "target": "estorides_core/cases.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/discoverer.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/discoverer.py", "target": "estorides_core/job_registry.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/discoverer.py", "target": "estorides_core/pivot_engine.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/discoverer.py", "target": "estorides_core/graph_kuzu.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/discoverer.py", "target": "estorides_core/orchestrator.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/discoverer.py", "target": "estorides_core/orchestrator.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/entity_extraction.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/entity_resolution.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/entity_resolution.py", "target": "estorides_core/entity_extraction.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/entity_resolution.py", "target": "estorides_core/ids.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/entity_resolution.py", "target": "estorides_core/transliteration.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/entity_store.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/entity_store.py", "target": "estorides_core/entity_resolution.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/entity_store.py", "target": "estorides_core/sqlite_store.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/feeds.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/feeds.py", "target": "estorides_core/ssrf_guard.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/fusion_store.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/fusion_store.py", "target": "estorides_core/ids.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/fusion_store.py", "target": "estorides_core/reliability_scoring.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/fusion_store.py", "target": "estorides_core/sqlite_store.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/fusion_store.py", "target": "estorides_core/entity_resolution.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/graph_bundle.py", "target": "estorides_core/graph_force.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/graph_kuzu.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/hypothesis_engine.py", "target": "estorides_core/ids.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/hypothesis_engine.py", "target": "estorides_core/reliability_scoring.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/hypothesis_engine.py", "target": "estorides_core/reliability_scoring.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/intel_resolver.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/intel_resolver.py", "target": "estorides_core/ssrf_guard.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/intel_resolver.py", "target": "estorides_core/ontology.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/knowledge_graph.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/knowledge_graph.py", "target": "estorides_core/entity_extraction.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/lan_bundle.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/lan_runner.py", "target": "estorides_core/tool_runner.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/monitoring.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/monitoring.py", "target": "estorides_core/sqlite_store.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/observation_models.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/ontology.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/ontology.py", "target": "estorides_core/ssrf_guard.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/ops_observability.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/orchestrator.py", "target": "estorides_llm/__init__.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/orchestrator.py", "target": "estorides_core/async_client.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/orchestrator.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/orchestrator.py", "target": "estorides_core/entity_extraction.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/orchestrator.py", "target": "estorides_core/event_bus.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/orchestrator.py", "target": "estorides_core/knowledge_graph.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/orchestrator.py", "target": "estorides_core/mitre_attack.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/orchestrator.py", "target": "estorides_core/ontology.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/orchestrator.py", "target": "estorides_core/parsers.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/orchestrator.py", "target": "estorides_core/recon_fusion.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/orchestrator.py", "target": "estorides_core/relationship_inference.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/orchestrator.py", "target": "estorides_core/source_loader.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/orchestrator.py", "target": "estorides_core/system_app_sources.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/orchestrator.py", "target": "estorides_core/graph_kuzu.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/orchestrator.py", "target": "estorides_core/cases.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/orchestrator.py", "target": "estorides_core/intel_resolver.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/orchestrator.py", "target": "estorides_core/entity_resolution.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/orchestrator.py", "target": "estorides_core/fusion_store.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/orchestrator.py", "target": "estorides_core/fusion_store.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/orchestrator.py", "target": "estorides_core/entity_store.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/orchestrator.py", "target": "estorides_core/system_app_sources.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/orchestrator.py", "target": "estorides_core/pagination.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/orchestrator.py", "target": "estorides_core/pagination.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/osiris_sources.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/osiris_sources.py", "target": "estorides_core/ssrf_guard.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/pivot_engine.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/recon_fusion.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/recon_fusion.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/recon_fusion.py", "target": "estorides_core/ids.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/recon_fusion.py", "target": "estorides_core/reliability_scoring.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/recon_pipeline.py", "target": "estorides_core/tech_fingerprint.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/recon_pipeline.py", "target": "estorides_core/vuln_correlation.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/recon_pipeline.py", "target": "estorides_core/cloud_asset_discovery.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/recon_pipeline.py", "target": "estorides_core/people_intel.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/recon_pipeline.py", "target": "estorides_core/code_exposure.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/recon_pipeline.py", "target": "estorides_core/supply_chain.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/recon_pipeline.py", "target": "estorides_core/pdns_monitor.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/search_telemetry.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/source_loader.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/system_app_sources.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/system_app_sources.py", "target": "estorides_core/parsers.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/system_app_sources.py", "target": "estorides_core/tool_runner.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/system_app_sources.py", "target": "estorides_core/tool_runner.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/tool_install.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/tool_install.py", "target": "estorides_core/tool_runner.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/tool_install.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/tool_runner.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/tool_runner.py", "target": "estorides_core/entity_extraction.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/transforms.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/transforms.py", "target": "estorides_core/intel_resolver.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_core/validation.py", "target": "estorides_core/entity_extraction.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_export/__init__.py", "target": "estorides_export/stix.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_export/__init__.py", "target": "estorides_export/misp.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_export/__init__.py", "target": "estorides_export/encryption.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_export/__init__.py", "target": "estorides_export/report.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_export/__init__.py", "target": "estorides_export/recon_report.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_export/encryption.py", "target": "estorides_core/knowledge_graph.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_export/encryption.py", "target": "estorides_export/__init__.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_export/encryption.py", "target": "estorides_export/__init__.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_export/misp.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_export/misp.py", "target": "estorides_core/knowledge_graph.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_export/stix.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_export/stix.py", "target": "estorides_core/knowledge_graph.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_llm/__init__.py", "target": "estorides_llm/manager.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_llm/manager.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_llm/manager.py", "target": "estorides_llm/intelligence_prompts.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/audit.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/discoverer.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/discoverer.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/entity_extraction.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/feeds.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/job_registry.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/knowledge_graph.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/lan_web.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/lan_web.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/openapi.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/ops_observability.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/orchestrator.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/pivot_engine.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/search_telemetry.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/validation.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/web_security.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_export/__init__.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_export/encryption.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_web_tools.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/graph_rag_search.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/knowledge_graph.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/__init__.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/lan_web.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/__init__.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/entity_extraction.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/cases.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/graph_kuzu.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/intel_resolver.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/fusion_store.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/socmint.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/monitoring.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/monitoring.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/monitoring.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/alerter.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/monitoring.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/transforms.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/transforms.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/__init__.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/graph_force.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/__init__.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/fusion_analytics.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/entity_extraction.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/graph_kuzu.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web.py", "target": "estorides_core/orchestrator.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web_tools.py", "target": "estorides_core/audit.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web_tools.py", "target": "estorides_core/tool_install.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web_tools.py", "target": "estorides_core/web_security.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "estorides_web_tools.py", "target": "estorides_web.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "static/js/estorides.js", "target": "estorides_core/scope.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "static/js/estorides.js", "target": "estorides_export/report.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "static/js/estorides.js", "target": "estorides_core/discoverer.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/properties/test_change_detection_properties.py", "target": "estorides_core/change_detection.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/properties/test_csp_safe_styles_properties.py", "target": "estorides_core/web_security.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/properties/test_graph_bundle_properties.py", "target": "estorides_core/graph_bundle.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/properties/test_hypothesis_engine_properties.py", "target": "estorides_core/hypothesis_engine.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/properties/test_observation_models_properties.py", "target": "estorides_core/observation_models.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/properties/test_parsers_properties.py", "target": "estorides_core/parsers.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/properties/test_recon_fusion_properties.py", "target": "estorides_core/recon_fusion.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/properties/test_reliability_scoring_properties.py", "target": "estorides_core/reliability_scoring.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/properties/test_reliability_scoring_properties.py", "target": "estorides_core/reliability_scoring.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/properties/test_reliability_scoring_properties.py", "target": "estorides_core/reliability_scoring.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/properties/test_reliability_scoring_properties.py", "target": "estorides_core/reliability_scoring.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/properties/test_reliability_scoring_properties.py", "target": "estorides_core/reliability_scoring.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/properties/test_reliability_scoring_properties.py", "target": "estorides_core/reliability_scoring.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/properties/test_reliability_scoring_properties.py", "target": "estorides_core/reliability_scoring.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/properties/test_search_telemetry_properties.py", "target": "estorides_core/search_telemetry.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/properties/test_source_health_monitoring_properties.py", "target": "estorides_core/source_health_monitoring.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/properties/test_system_app_sources_properties.py", "target": "estorides_core/system_app_sources.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/properties/test_system_app_sources_properties.py", "target": "estorides_core/parsers.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/properties/test_system_app_sources_properties.py", "target": "estorides_core/tool_runner.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/properties/test_tool_runner_properties.py", "target": "estorides_core/tool_runner.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_active_recon.py", "target": "estorides_core/active_recon.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_active_recon.py", "target": "estorides_core/tool_runner.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_async_client.py", "target": "estorides_core/async_client.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_async_client.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_audit_log.py", "target": "estorides_core/audit.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_auth_gate.py", "target": "estorides_core/web_security.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_auth_gate.py", "target": "estorides_core/web_security.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_case_crypto.py", "target": "estorides_core/case_crypto.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_case_crypto.py", "target": "estorides_core/cases.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_central_config.py", "target": "estorides_core/__init__.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_central_config.py", "target": "estorides_core/__init__.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_central_config.py", "target": "estorides_core/orchestrator.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_central_config.py", "target": "estorides_core/tool_install.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_change_detection.py", "target": "estorides_core/change_detection.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_change_detection.py", "target": "estorides_core/reliability_scoring.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_cli_watch.py", "target": "estorides_cli.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_cli_watch.py", "target": "estorides_core/monitoring.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_cli_watch.py", "target": "estorides_core/monitoring.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_cli_watch.py", "target": "estorides_cli.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_cli_watch.py", "target": "estorides_core/monitoring.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_cloud_asset_discovery.py", "target": "estorides_core/cloud_asset_discovery.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_cloud_asset_discovery.py", "target": "estorides_core/cloud_asset_discovery.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_code_exposure.py", "target": "estorides_core/code_exposure.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_config_env.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_config_env.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_config_env.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_config_env.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_config_env.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_config_env.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_csp_safe_styles.py", "target": "estorides_core/search_telemetry.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_csp_safe_styles.py", "target": "estorides_core/web_security.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_csp_safe_styles.py", "target": "estorides_core/web_security.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_encrypted_export.py", "target": "estorides_core/entity_extraction.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_encrypted_export.py", "target": "estorides_core/knowledge_graph.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_encrypted_export.py", "target": "estorides_export/encryption.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_encrypted_export.py", "target": "estorides_export/encryption.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_encrypted_export.py", "target": "estorides_export/encryption.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_entity_extraction.py", "target": "estorides_core/entity_extraction.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_entity_resolution.py", "target": "estorides_core/entity_extraction.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_entity_resolution.py", "target": "estorides_core/entity_resolution.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_entity_resolution.py", "target": "estorides_core/entity_store.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_entity_resolution.py", "target": "estorides_core/transliteration.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_event_bus.py", "target": "estorides_core/event_bus.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_event_bus.py", "target": "estorides_core/event_bus.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_event_bus.py", "target": "estorides_core/event_bus.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_event_bus.py", "target": "estorides_core/event_bus.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_event_bus.py", "target": "estorides_core/event_bus.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_fusion_analytics.py", "target": "estorides_core/fusion_analytics.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_fusion_analytics.py", "target": "estorides_core/fusion_store.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_graph_bundle.py", "target": "estorides_core/graph_bundle.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_graph_bundle.py", "target": "estorides_core/graph_bundle.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_graph_bundle.py", "target": "estorides_core/graph_bundle.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_graph_bundle.py", "target": "estorides_core/__init__.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_graph_bundle.py", "target": "estorides_core/graph_force.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_graph_bundle.py", "target": "estorides_core/graph_force.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_graph_bundle.py", "target": "estorides_core/__init__.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_graph_bundle.py", "target": "estorides_core/graph_bundle.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_graph_bundle.py", "target": "estorides_core/graph_bundle.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_graph_bundle.py", "target": "estorides_core/graph_bundle.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_graph_bundle.py", "target": "estorides_core/graph_force.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_graph_bundle.py", "target": "estorides_core/graph_bundle.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_graph_bundle.py", "target": "estorides_core/graph_bundle.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_graph_bundle.py", "target": "estorides_core/graph_bundle.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_graph_bundle.py", "target": "estorides_core/graph_bundle.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_graph_force3d.py", "target": "estorides_core/graph_force.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_graph_rag_search.py", "target": "estorides_core/graph_rag_search.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_graph_rag_search.py", "target": "estorides_web.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_hardening.py", "target": "estorides_core/cases.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_hardening.py", "target": "estorides_core/web_security.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_hardening.py", "target": "estorides_export/report.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_hypothesis_engine.py", "target": "estorides_core/hypothesis_engine.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_hypothesis_engine.py", "target": "estorides_core/reliability_scoring.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_ids.py", "target": "estorides_core/ids.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_job_registry.py", "target": "estorides_core/job_registry.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_keyless_sources.py", "target": "estorides_core/orchestrator.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_keyless_sources.py", "target": "estorides_core/parsers.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_keyless_sources.py", "target": "estorides_core/parsers.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_keyless_sources.py", "target": "estorides_core/parsers.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_lan_bundle.py", "target": "estorides_core/__init__.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_lan_bundle.py", "target": "estorides_core/lan_inventory.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_lan_inventory.py", "target": "estorides_core/__init__.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_lan_policy.py", "target": "estorides_core/__init__.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_lan_runner.py", "target": "estorides_core/__init__.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_lan_runner.py", "target": "estorides_core/tool_runner.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_lan_scope.py", "target": "estorides_core/__init__.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_lan_web.py", "target": "estorides_core/__init__.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_map_basemap.py", "target": "estorides_core/web_security.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_monitoring.py", "target": "estorides_core/alerter.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_monitoring.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_monitoring.py", "target": "estorides_core/monitoring.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_monitoring.py", "target": "estorides_core/source_loader.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_obs_fts.py", "target": "estorides_core/cases.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_observation_models.py", "target": "estorides_core/observation_models.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_openapi.py", "target": "estorides_web.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_ops_observability.py", "target": "estorides_core/ops_observability.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_ops_observability.py", "target": "estorides_core/ops_observability.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_ops_observability.py", "target": "estorides_core/ops_observability.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_ops_observability.py", "target": "estorides_core/ops_observability.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_ops_observability.py", "target": "estorides_core/ops_observability.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_ops_observability.py", "target": "estorides_core/ops_observability.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_opsec_contact.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_opsec_contact.py", "target": "estorides_core/orchestrator.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_opsec_contact.py", "target": "estorides_core/source_loader.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_orchestrator_fanout.py", "target": "estorides_core/__init__.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_pagination.py", "target": "estorides_core/pagination.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_parsers.py", "target": "estorides_core/__init__.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_pdns_monitor.py", "target": "estorides_core/pdns_monitor.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_pdns_monitor.py", "target": "estorides_core/pdns_monitor.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_people_intel.py", "target": "estorides_core/people_intel.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_probabilistic_fusion.py", "target": "estorides_core/fusion_store.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_probabilistic_fusion.py", "target": "estorides_core/fusion_store.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_query_intent.py", "target": "estorides_core/entity_extraction.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_recon_fusion.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_recon_fusion.py", "target": "estorides_core/recon_fusion.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_recon_report.py", "target": "estorides_export/recon_report.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_recon_report.py", "target": "estorides_export/recon_report.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_reliability_scoring.py", "target": "estorides_core/reliability_scoring.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_retry_policy.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_retry_policy.py", "target": "estorides_core/__init__.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_retry_policy.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_scope.py", "target": "estorides_core/scope.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_search_telemetry.py", "target": "estorides_core/search_telemetry.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_security_remediation.py", "target": "estorides_core/ssrf_guard.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_security_remediation.py", "target": "estorides_core/web_security.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_security_remediation.py", "target": "estorides_core/__init__.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_security_remediation.py", "target": "estorides_core/__init__.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_security_remediation.py", "target": "estorides_core/alerter.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_security_remediation.py", "target": "estorides_core/alerter.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_security_remediation.py", "target": "estorides_core/alerter.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_security_remediation.py", "target": "estorides_core/alerter.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_security_remediation.py", "target": "estorides_core/__init__.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_security_remediation.py", "target": "estorides_core/alerter.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_security_remediation.py", "target": "estorides_core/alerter.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_security_remediation.py", "target": "estorides_core/__init__.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_security_remediation.py", "target": "estorides_core/ssrf_guard.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_socmint.py", "target": "estorides_core/entity_extraction.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_socmint.py", "target": "estorides_core/parsers.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_socmint.py", "target": "estorides_core/socmint.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_socmint.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_socmint.py", "target": "estorides_core/source_loader.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_socmint.py", "target": "estorides_core/socmint.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_socmint.py", "target": "estorides_core/socmint.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_source_health_monitoring.py", "target": "estorides_core/source_health_monitoring.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_source_loader.py", "target": "estorides_core/source_loader.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_source_routing.py", "target": "estorides_core/orchestrator.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_source_routing.py", "target": "estorides_core/orchestrator.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_sqlite_store.py", "target": "estorides_core/sqlite_store.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_structured_extraction.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_structured_extraction.py", "target": "estorides_core/entity_extraction.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_structured_extraction.py", "target": "estorides_core/pivot_engine.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_supply_chain.py", "target": "estorides_core/supply_chain.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_system_app_sources.py", "target": "estorides_core/source_loader.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_system_app_sources.py", "target": "estorides_core/system_app_sources.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_system_app_sources.py", "target": "estorides_core/tool_runner.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_system_app_sources.py", "target": "estorides_core/system_app_sources.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_system_app_sources.py", "target": "estorides_core/system_app_sources.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_system_app_sources.py", "target": "estorides_core/system_app_sources.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_system_app_sources.py", "target": "estorides_core/system_app_sources.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_system_app_sources.py", "target": "estorides_core/system_app_sources.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_system_app_sources.py", "target": "estorides_core/orchestrator.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_system_app_sources.py", "target": "estorides_core/orchestrator.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_system_app_sources.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_system_app_sources.py", "target": "estorides_core/system_app_sources.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_system_app_sources.py", "target": "estorides_core/system_app_sources.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_system_app_sources.py", "target": "estorides_core/orchestrator.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_system_app_sources.py", "target": "estorides_core/source_loader.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_tech_fingerprint.py", "target": "estorides_core/tech_fingerprint.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_tool_doctor.py", "target": "estorides_core/tool_install.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_tool_doctor.py", "target": "estorides_core/tool_install.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_tool_doctor.py", "target": "estorides_core/tool_install.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_tool_doctor.py", "target": "estorides_core/tool_install.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_tool_install.py", "target": "estorides_core/tool_install.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_tool_runner.py", "target": "estorides_core/config.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_tool_runner.py", "target": "estorides_core/tool_runner.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_tool_runner.py", "target": "estorides_core/validation.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_transforms.py", "target": "estorides_core/transforms.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_transforms.py", "target": "estorides_core/intel_resolver.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_transforms.py", "target": "estorides_core/transforms.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_transforms.py", "target": "estorides_core/transforms.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_transforms.py", "target": "estorides_core/transforms.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_transforms.py", "target": "estorides_core/transforms.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_transforms.py", "target": "estorides_core/transforms.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_ui_professional.py", "target": "estorides_core/recon_fusion.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_ui_professional.py", "target": "estorides_core/search_telemetry.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_vuln_correlation.py", "target": "estorides_core/vuln_correlation.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_web_helpers.py", "target": "estorides_core/web_security.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_web_helpers.py", "target": "estorides_web.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_web_tools_blueprint.py", "target": "estorides_web.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_web_tools_blueprint.py", "target": "estorides_web.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_web_tools_blueprint.py", "target": "estorides_web.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tests/test_web_tools_blueprint.py", "target": "estorides_web.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "tools/sync_docs.py", "target": "estorides_web.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "web.py", "target": "wsgi.py"}, {"confidence": "EXTRACTED", "relation": "resolved_imports", "source": "wsgi.py", "target": "estorides_web.py"}], "generator": "readmenator", "metadata": {"edge_count": 18387, "file_count": 191, "language_count": 3, "symbol_count": 3183}, "nodes": [{"id": ".scratchpad/gb_shots.py", "kind": "module", "label": "gb_shots.py", "language": "py", "sha256": "ec3d2b49fd4ae907", "symbol_count": 0, "symbols": []}, {"id": ".scratchpad/gfv2_bridge.py", "kind": "module", "label": "gfv2_bridge.py", "language": "py", "sha256": "e51e0b7b8a548475", "symbol_count": 0, "symbols": []}, {"id": ".scratchpad/gfv2_click.py", "kind": "module", "label": "gfv2_click.py", "language": "py", "sha256": "ca1fa7103f3de6db", "symbol_count": 0, "symbols": []}, {"id": ".scratchpad/gfv2_data.py", "kind": "module", "label": "gfv2_data.py", "language": "py", "sha256": "afa1638076dded35", "symbol_count": 0, "symbols": []}, {"id": ".scratchpad/gfv2_debug.py", "kind": "module", "label": "gfv2_debug.py", "language": "py", "sha256": "d8953589e09d8a79", "symbol_count": 0, "symbols": []}, {"id": ".scratchpad/gfv2_dom.py", "kind": "module", "label": "gfv2_dom.py", "language": "py", "sha256": "c77d9b2d87c58f35", "symbol_count": 0, "symbols": []}, {"id": ".scratchpad/gfv2_func.py", "kind": "module", "label": "gfv2_func.py", "language": "py", "sha256": "9640cea596e9f88e", "symbol_count": 0, "symbols": []}, {"id": ".scratchpad/gfv2_raw.py", "kind": "module", "label": "gfv2_raw.py", "language": "py", "sha256": "6c0ec7e7f2e8723a", "symbol_count": 0, "symbols": []}, {"id": ".scratchpad/gfv2_shots.py", "kind": "module", "label": "gfv2_shots.py", "language": "py", "sha256": "2973a58e45ba9a74", "symbol_count": 0, "symbols": []}, {"id": ".scratchpad/gfv2_step.py", "kind": "module", "label": "gfv2_step.py", "language": "py", "sha256": "4d2211ad8fbe9b39", "symbol_count": 0, "symbols": []}, {"id": ".scratchpad/gfv2_time.py", "kind": "module", "label": "gfv2_time.py", "language": "py", "sha256": "292b2a6fc4d24ad5", "symbol_count": 0, "symbols": []}, {"id": "_multi_test.sh", "kind": "module", "label": "_multi_test.sh", "language": "sh", "sha256": "9dc8db888b0c882a", "symbol_count": 0, "symbols": []}, {"doc": "Deprecated entry point. Use:  - the `estorides` console script (installed by `pip install -e .`), or - `python3 estorides_cli.py serve` for the dev server, or - `gunicorn -w 4 wsgi:app` for production.  This file is kept as a redirect so old launchers that import `app` from this module keep working. It intentionally does NOT start a server on import.", "id": "app.py", "kind": "module", "label": "app.py", "language": "py", "sha256": "57b21bdb023585b8", "symbol_count": 0, "symbols": []}, {"doc": "estorides CLI.  Usage: estorides \"example.com\" estorides \"8.8.8.8\" --include-paid estorides \"user@example.com\" --only-sources shodan_internetdb,ipapi_free estorides graph --export graphml estorides report --query \"example.com\" --out report.md estorides stix --out bundle.json estorides serve --port 5050", "id": "estorides_cli.py", "kind": "module", "label": "estorides_cli.py", "language": "py", "sha256": "77574ff5622848fb", "symbol_count": 31, "symbols": [{"kind": "function", "line": 39, "name": "_setup_logging", "signature": "def _setup_logging(verbose)"}, {"doc": "Group discovered entity values by type for the requested type set.\n\nUsed to surface the human selectors (emails, usernames, persons, orgs,\nphones) a discover run found, separately from the infrastructure list.", "kind": "function", "line": 46, "name": "_collect_selectors", "signature": "def _collect_selectors(events, types)"}, {"doc": "Resolve the egress proxy from the OPSEC flags.\n\n`--tor` is a convenience alias for the default local Tor SOCKS port;\nan explicit `--proxy` wins over it. Returns None when neither is set,\nin which case the engine still honours the env-configured proxy pool.", "kind": "function", "line": 62, "name": "_resolve_proxy", "signature": "def _resolve_proxy(args)"}, {"doc": "Attach the shared operator-OPSEC flags to a subcommand parser.", "kind": "function", "line": 75, "name": "_add_opsec_flags", "signature": "def _add_opsec_flags(parser)"}, {"doc": "v1.2 — fanout the surface from a seed.\n\nMirrors the /api/discover/start endpoint but as a CLI subcommand\nso an operator can drop a seed in a terminal and walk away.\nStreams progress to stdout. The final case is dumped to\n--out-json if provided.", "kind": "function", "line": 93, "name": "cmd_discover", "signature": "def cmd_discover(args)"}, {"kind": "function", "line": 205, "name": "cmd_run", "signature": "def cmd_run(args)"}, {"doc": "Classify discovered assets against a program's scope rules.\n\nReads assets (a `discover --out-json` surface or a flat host list),\napplies the in/out-of-scope rules, and emits the in-scope host/IP\nlists an operator pipes into the active phase. Out-of-scope assets are\nsurfaced explicitly so they are never targeted by accident.", "kind": "function", "line": 282, "name": "cmd_scope", "signature": "def cmd_scope(args)"}, {"kind": "function", "line": 328, "name": "cmd_graph_export", "signature": "def cmd_graph_export(args)"}, {"kind": "function", "line": 357, "name": "cmd_export_stix", "signature": "def cmd_export_stix(args)"}, {"kind": "function", "line": 367, "name": "cmd_export_misp", "signature": "def cmd_export_misp(args)"}, {"doc": "Render a Markdown report for a case.\n\nReads from the case store (so the report reflects what was persisted,\nnot the volatile in-memory graph). When `--diff <other_id>` is given,\nthe report also includes a \"what's new\" section vs the other case.", "kind": "function", "line": 377, "name": "cmd_report", "signature": "def cmd_report(args)"}, {"doc": "Diff two cases. CLI twin of /api/cases/diff.", "kind": "function", "line": 418, "name": "cmd_diff", "signature": "def cmd_diff(args)"}, {"kind": "function", "line": 446, "name": "cmd_status", "signature": "def cmd_status(_)"}, {"doc": "Query the cross-run fusion datastore.\n\nSubactions:\n  stats              size of the fused fact base\n  sources            YAML catalogue with fetch/ok counters\n  entities [TERM]    search fused entities (``--type``, ``--min-sources``)\n  entity ID          full fused view of one entity (provenance + props)", "kind": "function", "line": 453, "name": "cmd_fusion", "signature": "def cmd_fusion(args)"}, {"doc": "Add a new recurring watch target.", "kind": "function", "line": 496, "name": "cmd_watch_add", "signature": "def cmd_watch_add(args)"}, {"doc": "Create an async watch runner wired to the Orchestrator.\n\nReturns an async function that takes a WatchTarget and returns\nthe orchestrator's run result dict.", "kind": "function", "line": 538, "name": "_watch_runner_factory", "signature": "def _watch_runner_factory(proxy, passive_only)"}, {"doc": "List all watch targets.", "kind": "function", "line": 560, "name": "cmd_watch_list", "signature": "def cmd_watch_list(args)"}, {"doc": "Delete a watch target.", "kind": "function", "line": 580, "name": "cmd_watch_remove", "signature": "def cmd_watch_remove(args)"}, {"kind": "function", "line": 592, "name": "cmd_watch_enable", "signature": "def cmd_watch_enable(args)"}, {"kind": "function", "line": 605, "name": "cmd_watch_disable", "signature": "def cmd_watch_disable(args)"}, {"kind": "function", "line": 617, "name": "cmd_watch_history", "signature": "def cmd_watch_history(args)"}, {"kind": "function", "line": 638, "name": "cmd_alerts_test", "signature": "def cmd_alerts_test(args)"}, {"kind": "function", "line": 650, "name": "cmd_alerts_channels", "signature": "def cmd_alerts_channels(args)"}, {"kind": "function", "line": 662, "name": "cmd_scheduler_start", "signature": "def cmd_scheduler_start(args)"}, {"kind": "function", "line": 672, "name": "cmd_scheduler_stop", "signature": "def cmd_scheduler_stop(args)"}, {"kind": "function", "line": 682, "name": "cmd_scheduler_status", "signature": "def cmd_scheduler_status(args)"}, {"kind": "function", "line": 690, "name": "cmd_serve", "signature": "def cmd_serve(args)"}, {"kind": "function", "line": 707, "name": "build_parser", "signature": "def build_parser()"}, {"kind": "function", "line": 863, "name": "main", "signature": "def main(argv)"}, {"kind": "function", "line": 222, "name": "_on_done", "signature": "def _on_done(source_name, ok, status, elapsed_ms)"}, {"kind": "function", "line": 546, "name": "_run", "signature": "def _run(watch)"}]}, {"doc": "estorides_core.__init__", "id": "estorides_core/__init__.py", "kind": "module", "label": "__init__.py", "language": "py", "sha256": "53c9158fe8fcf09c", "symbol_count": 0, "symbols": []}, {"id": "estorides_core/active_recon.py", "kind": "module", "label": "active_recon.py", "language": "py", "sha256": "866df9d2d8701221", "symbol_count": 25, "symbols": [{"kind": "class", "line": 13, "name": "NmapResult", "signature": "class NmapResult"}, {"kind": "class", "line": 32, "name": "NiktoResult", "signature": "class NiktoResult"}, {"kind": "class", "line": 49, "name": "SqlmapResult", "signature": "class SqlmapResult"}, {"kind": "class", "line": 66, "name": "DnsreconResult", "signature": "class DnsreconResult"}, {"kind": "class", "line": 85, "name": "TheHarvesterResult", "signature": "class TheHarvesterResult"}, {"kind": "method", "line": 102, "name": "_parse_nmap_stdout", "signature": "def _parse_nmap_stdout(stdout, tool_name)"}, {"kind": "method", "line": 125, "name": "_parse_nikto_stdout", "signature": "def _parse_nikto_stdout(stdout, tool_name)"}, {"kind": "method", "line": 144, "name": "_parse_sqlmap_stdout", "signature": "def _parse_sqlmap_stdout(stdout, tool_name)"}, {"kind": "method", "line": 171, "name": "_parse_dnsrecon_stdout", "signature": "def _parse_dnsrecon_stdout(stdout, tool_name)"}, {"kind": "method", "line": 188, "name": "_parse_harvester_stdout", "signature": "def _parse_harvester_stdout(stdout, tool_name)"}, {"kind": "method", "line": 213, "name": "run_nmap", "signature": "def run_nmap(target, args)"}, {"kind": "method", "line": 243, "name": "run_nikto", "signature": "def run_nikto(target, args)"}, {"kind": "method", "line": 269, "name": "run_sqlmap", "signature": "def run_sqlmap(target, args)"}, {"kind": "method", "line": 295, "name": "run_dnsrecon", "signature": "def run_dnsrecon(target, args)"}, {"kind": "method", "line": 325, "name": "run_theHarvester", "signature": "def run_theHarvester(target, args)"}, {"kind": "method", "line": 24, "name": "to_dict", "signature": "def to_dict(self)"}, {"kind": "method", "line": 27, "name": "to_entities", "signature": "def to_entities(self)"}, {"kind": "method", "line": 41, "name": "to_dict", "signature": "def to_dict(self)"}, {"kind": "method", "line": 44, "name": "to_entities", "signature": "def to_entities(self)"}, {"kind": "method", "line": 58, "name": "to_dict", "signature": "def to_dict(self)"}, {"kind": "method", "line": 61, "name": "to_entities", "signature": "def to_entities(self)"}, {"kind": "method", "line": 77, "name": "to_dict", "signature": "def to_dict(self)"}, {"kind": "method", "line": 80, "name": "to_entities", "signature": "def to_entities(self)"}, {"kind": "method", "line": 95, "name": "to_dict", "signature": "def to_dict(self)"}, {"kind": "method", "line": 98, "name": "to_entities", "signature": "def to_entities(self)"}]}, {"doc": "estorides_core.alerter ====================== Alert channel dispatcher for watch target notifications.  Supports: * Slack webhook * Discord webhook (embedded messages) * Telegram bot * Email (SMTP) * Generic webhook (HTTP POST)  Each channel reads its credentials from environment variables so they are never stored in the database.", "id": "estorides_core/alerter.py", "kind": "module", "label": "alerter.py", "language": "py", "sha256": "bfb3d4bd946e9d48", "symbol_count": 15, "symbols": [{"kind": "function", "line": 42, "name": "_check_cooldown", "signature": "def _check_cooldown(channel)"}, {"doc": "Refuse every HTTP redirect.\n\n``urlopen`` follows 301/302/307/308 by default, which would let a\nmalicious webhook pivot to an SSRF-blocked target (e.g. cloud metadata\nat 169.254.169.254) *after* passing the pre-flight ``check_url`` guard.\nReturning ``None`` makes the opener raise ``HTTPError`` instead of\nissuing a second request. Alert delivery fails closed; legitimate\nwebhook endpoints (Slack/Discord/Telegram) do not redirect POSTs.", "kind": "class", "line": 53, "name": "_NoRedirectHandler", "signature": "class _NoRedirectHandler(HTTPRedirectHandler)"}, {"doc": "POST JSON payload to URL, return True on success.\n\nThe destination is treated as hostile: every webhook URL comes from\noperator-owned environment variables (never from a caller-supplied\nstring) and is validated by the central SSRF guard before any socket\nis opened. Raw caller-supplied URLs are refused outright (CodeQL #47):\na ``channel`` starting with ``http`` can never become a request\ndestination. Redirects are never followed (see ``_NoRedirectHandler``):\nthe guard vetted the first hop only.", "kind": "method", "line": 77, "name": "_http_post", "signature": "def _http_post(url, payload)"}, {"doc": "Send a Slack message via Incoming Webhook.", "kind": "method", "line": 111, "name": "_send_slack", "signature": "def _send_slack(webhook_url, title, body, severity)"}, {"doc": "Send a Discord embed via Webhook.", "kind": "method", "line": 126, "name": "_send_discord", "signature": "def _send_discord(webhook_url, title, body, severity)"}, {"doc": "Send a Telegram message via Bot API.", "kind": "method", "line": 141, "name": "_send_telegram", "signature": "def _send_telegram(bot_token, chat_id, title, body, severity)"}, {"doc": "Send an email alert via SMTP.", "kind": "method", "line": 156, "name": "_send_email", "signature": "def _send_email(smtp_host, smtp_port, smtp_user, smtp_pass, from_addr, to_addr, title, body, severity)"}, {"doc": "Send a generic webhook POST.", "kind": "method", "line": 177, "name": "_send_webhook", "signature": "def _send_webhook(webhook_url, title, body, severity)"}, {"doc": "Central alert dispatcher: routes alerts to configured channels.\n\nReads channel credentials from environment variables on each dispatch\nso an operator can hot-configure without restarting.", "kind": "class", "line": 194, "name": "AlertDispatcher", "signature": "class AlertDispatcher"}, {"kind": "method", "line": 314, "name": "_fmt_time", "signature": "def _fmt_time(ts)"}, {"kind": "method", "line": 64, "name": "redirect_request", "signature": "def redirect_request(self, req, fp, code, msg, headers, newurl)"}, {"doc": "Send an alert to a single channel. Returns True on success.", "kind": "method", "line": 201, "name": "send", "signature": "def send(self, channel, title, body, severity)"}, {"doc": "Send alerts for a completed watch run to all configured channels.", "kind": "method", "line": 260, "name": "send_watch_alert", "signature": "def send_watch_alert(self, watch, entity_count, obs_count, new_entities)"}, {"doc": "Send a test alert to verify channel configuration.", "kind": "method", "line": 280, "name": "test", "signature": "def test(self, channel)"}, {"doc": "Return list of configured channels with their status.", "kind": "method", "line": 290, "name": "available_channels", "signature": "def available_channels(self)"}]}, {"doc": "estorides_core.async_client =========================== Async HTTP client with: * per-host concurrency limits * exponential backoff * circuit breaker (per host) * disk-backed cache (SQLite) * automatic key-substitution from environment  Designed so a single query can fan out to 50+ sources in parallel without DOSsing any of them.", "id": "estorides_core/async_client.py", "kind": "module", "label": "async_client.py", "language": "py", "sha256": "25c9162f9f35234a", "symbol_count": 21, "symbols": [{"doc": "True when the proxy URL is a SOCKS proxy (e.g. Tor).", "kind": "function", "line": 49, "name": "_is_socks", "signature": "def _is_socks(proxy)"}, {"doc": "Strip any `user:pass@` credentials from a proxy URL before logging.", "kind": "function", "line": 54, "name": "_redact_proxy", "signature": "def _redact_proxy(proxy)"}, {"doc": "Per-host circuit breaker.", "kind": "class", "line": 64, "name": "CircuitBreaker", "signature": "class CircuitBreaker"}, {"doc": "SQLite-backed response cache. Key = (url + method + body hash).\n\nEntries carry their write timestamp and are only served while younger\nthan `ttl_seconds`; a stale row is ignored (and lazily overwritten on\nthe next live fetch) so the cache can never pin down OSINT that has\nsince changed or been taken down.", "kind": "class", "line": 86, "name": "ResponseCache", "signature": "class ResponseCache"}, {"doc": "Async HTTP client with retries, backoff, circuit breaker, cache.", "kind": "class", "line": 172, "name": "AsyncClient", "signature": "class AsyncClient"}, {"kind": "method", "line": 400, "name": "sync_fetch", "signature": "def sync_fetch(method, url)"}, {"kind": "method", "line": 69, "name": "allow", "signature": "def allow(self, host)"}, {"kind": "method", "line": 75, "name": "record_success", "signature": "def record_success(self, host)"}, {"kind": "method", "line": 79, "name": "record_failure", "signature": "def record_failure(self, host)"}, {"kind": "method", "line": 95, "name": "__init__", "signature": "def __init__(self, path)"}, {"doc": "Short-lived connection that is always closed.\n\n`with sqlite3.connect(...)` only commits/rolls back — it does NOT\nclose the connection, so the old code leaked one connection per\ncache read/write until GC.", "kind": "method", "line": 109, "name": "_conn", "signature": "def _conn(self)"}, {"kind": "method", "line": 123, "name": "_init_db", "signature": "def _init_db(self)"}, {"kind": "method", "line": 136, "name": "_key", "signature": "def _key(method, url, body)"}, {"kind": "method", "line": 145, "name": "get", "signature": "def get(self, method, url, body)"}, {"kind": "method", "line": 161, "name": "set", "signature": "def set(self, method, url, body, value)"}, {"kind": "method", "line": 175, "name": "__init__", "signature": "def __init__(self)"}, {"kind": "method", "line": 206, "name": "__aenter__", "signature": "def __aenter__(self)"}, {"doc": "Round-robin the next HTTP proxy, or None (SOCKS/connector or direct).", "kind": "method", "line": 250, "name": "_next_http_proxy", "signature": "def _next_http_proxy(self)"}, {"kind": "method", "line": 258, "name": "__aexit__", "signature": "def __aexit__(self)"}, {"kind": "method", "line": 264, "name": "session", "signature": "def session(self)"}, {"doc": "Fetch a URL. Returns (parsed_data, meta).\n\nmeta contains status, content_type, cached, attempts, error.\nparsed_data is dict/list/str/None depending on content-type.", "kind": "method", "line": 270, "name": "fetch", "signature": "def fetch(self, method, url)"}]}, {"doc": "estorides_core.audit ==================== Append-only audit log + per-IP rate limiter for the web layer.  Two responsibilities:  AuditLog Every API call (query, run, export) gets a JSON line appended to `sessions/audit.jsonl` (one line per request) with: timestamp, remote IP, query, sources used, observation count, status, runtime ms, result size. This is the compliance trail that \"state level\" platforms must produce on demand.  The log is append-only and best-effort: a disk error never breaks the request, it just records the failure and moves on. Concurrent writers are safe via a process-level lock (we only have one web process in the default deployment; for multi-worker setups we'd switch to a queue, but the file size stays small enough that the current model is fine).  RateLimiter Simple sliding-window rate limiter, in-memory. Default: 30 requests per minute per IP. Configurable via env. The window is sliding rather than fixed-bucket so a burst of 30 right at the boundary doesn't get a free second batch.  This module is dependency-free. It does NOT replace a real WAF or production-grade rate limiter (Redis-backed, distributed). It is a defensible default for self-hosted deployments and a starting point", "id": "estorides_core/audit.py", "kind": "module", "label": "audit.py", "language": "py", "sha256": "c4a5e31d3e060002", "symbol_count": 11, "symbols": [{"kind": "class", "line": 55, "name": "AuditEvent", "signature": "class AuditEvent"}, {"doc": "Append-only JSONL audit log with a size cap.\n\nThread-safe via a process-level lock. The on-disk file is opened\nper-write so a long-running process never holds an exclusive handle\n(which would defeat any out-of-band log rotation tooling).\n\nIssue #48: the log has no built-in rotation, so a busy deployment\ngrows the file without bound. We apply a soft size cap\n(`ESTORIDES_AUDIT_MAX_BYTES`, default 50 MiB). When the cap is\nhit we rotate in place: rename the current file to\n`audit.jsonl.1` and start fresh. Older rotated copies are\nremoved so the directory doesn't grow without bound either —\nonly the active file and the previous rotation survive. The\ncap is checked before each write so the cost is one stat per\nrecord.", "kind": "class", "line": 72, "name": "AuditLog", "signature": "class AuditLog"}, {"doc": "In-process sliding-window rate limiter.\n\nThe bucket state is a `dict[str, deque[float]]` held under a\n`threading.Lock`. **The state is per-process** — when the\napplication is deployed with gunicorn's default multi-worker mode\n(`-w 4`, as documented in `wsgi.py`), each worker process has its\nown counter and the effective rate limit is `N_workers * limit`.\n\nFor a multi-worker deployment there are two safe options:\n\n1.  Set `ESTORIDES_RATE_LIMIT = ceil(desired_total / N_workers)`,\n    so the per-worker cap × worker count approximates the\n    documented limit. This is the recommended shape for a\n    single-tenant deployment behind a reverse proxy that does\n    not share state.\n2.  Swap this class for a Redis-backed implementation; the call\n    sites only depend on `allow()` returning a bool, so the swap\n    is local to this module. Issue #38 documents the trade-off.", "kind": "class", "line": 180, "name": "RateLimiter", "signature": "class RateLimiter"}, {"kind": "method", "line": 68, "name": "to_jsonl", "signature": "def to_jsonl(self)"}, {"kind": "method", "line": 90, "name": "__init__", "signature": "def __init__(self, path)"}, {"kind": "method", "line": 104, "name": "record", "signature": "def record(self, event)"}, {"doc": "If the active file is over the cap, rotate in place.\n\nCaller must hold `self._lock`. We use a per-write stat-then-\nwrite sequence; the race window (another process also writing)\nis acceptable for a single-tenant audit log — the worst case\nis a slightly oversized file, not data loss.", "kind": "method", "line": 115, "name": "_maybe_rotate_locked", "signature": "def _maybe_rotate_locked(self)"}, {"kind": "method", "line": 150, "name": "query", "signature": "def query(self, event)"}, {"kind": "method", "line": 201, "name": "__init__", "signature": "def __init__(self)"}, {"doc": "Return (allowed, retry_after_seconds).\n\n`retry_after_seconds` is 0 when allowed, otherwise the number of\nseconds the caller should wait before retrying.\n\nThe configured `max_requests` is re-read from the environment on\nevery call so an operator can hot-tune the limit without a\nprocess restart. (The window itself is stable for a process\nlifetime, which is fine for our deployment shape.)", "kind": "method", "line": 214, "name": "allow", "signature": "def allow(self, key)"}, {"kind": "method", "line": 238, "name": "reset", "signature": "def reset(self, key)"}]}, {"doc": "estorides_core.case_crypto ========================== Opt in field encryption for case notes and analysis at rest.  I read a Fernet key from ESTORIDES_CASE_KEY only. Absent, malformed or missing backend means disabled and plaintext. I never raise on crypto failure and I never log the key.", "id": "estorides_core/case_crypto.py", "kind": "module", "label": "case_crypto.py", "language": "py", "sha256": "bc51acca21887cf1", "symbol_count": 4, "symbols": [{"doc": "Return a Fernet instance when configured and available else None.", "kind": "function", "line": 19, "name": "_load_fernet", "signature": "def _load_fernet()"}, {"doc": "Report whether field encryption is active and why.", "kind": "function", "line": 36, "name": "crypto_status", "signature": "def crypto_status()"}, {"doc": "Encrypt plain text when enabled else return it unchanged.", "kind": "function", "line": 50, "name": "encrypt_text", "signature": "def encrypt_text(plain)"}, {"doc": "Decrypt a token, passing through plaintext and errors safely.", "kind": "function", "line": 65, "name": "decrypt_text", "signature": "def decrypt_text(token)"}]}, {"doc": "estorides_core.cases ==================== Run persistence — every intelligence query becomes a \"case\" that survives across sessions.  Why a separate module: The orchestrator already writes a JSONL dataset line per run, but that format is append-only and has no indexes. We need cheap lookups by query, by entity, by date range, and a way to reconstruct a previous case (observations + entities + graph + analysis) without grepping a file.  Schema:  cases         (id, query, query_type, created_at, status, notes, source_count, obs_count, entity_count, mitre_json, analysis_json, kg_path) observations  (id, case_id, source, category, parser, parsed_json, raw_text, meta_json, ontology_json, mitre_json) case_entities (case_id, type, value, source, confidence, sources_json)  `cases` is the parent; observations and case_entities have FK to it via ON DELETE CASCADE so dropping a case cleans up.  Public surface:  store = CaseStore() case_id = store.create_case(query, query_type) store.add_observation(case_id, observation)", "id": "estorides_core/cases.py", "kind": "module", "label": "cases.py", "language": "py", "sha256": "28f421ad4ea68adc", "symbol_count": 21, "symbols": [{"doc": "Thread-safe SQLite-backed case repository.\n\nSQLite is plenty for OSINT-sized workloads (a few thousand cases\nper operator per month) and avoids a separate service. The\nunderlying file is shared with the cache if you point both env\nvars at the same path; otherwise we live in `estorides_cases.sqlite`\nnext to it.", "kind": "class", "line": 112, "name": "CaseStore", "signature": "class CaseStore(SqliteStore)"}, {"doc": "Create base tables then best effort FTS index.", "kind": "method", "line": 136, "name": "_init_schema", "signature": "def _init_schema(self)"}, {"doc": "True when the FTS5 index exists and answers queries.", "kind": "method", "line": 147, "name": "fts_available", "signature": "def fts_available(self)"}, {"doc": "Full text search over observations ordered by rank.", "kind": "method", "line": 151, "name": "search_observations_fts", "signature": "def search_observations_fts(self, query, limit)"}, {"doc": "Open a new case and return its id (8-char slug).", "kind": "method", "line": 173, "name": "create_case", "signature": "def create_case(self, query, query_type, notes)"}, {"doc": "Persist a single observation row.\n\nThe full parsed/raw payload is JSON-encoded so we can\nreconstruct the run later without re-running the source.", "kind": "method", "line": 189, "name": "add_observation", "signature": "def add_observation(self, case_id, observation)"}, {"doc": "Persist the merged entity list. Duplicate (type, value) rows\nfor the same case are silently ignored — the PK is the guard.", "kind": "method", "line": 223, "name": "add_entities", "signature": "def add_entities(self, case_id, entities)"}, {"kind": "method", "line": 246, "name": "finalise", "signature": "def finalise(self, case_id, analysis, kg_path, mitre, source_count, obs_count, entity_count, status)"}, {"kind": "method", "line": 276, "name": "delete_case", "signature": "def delete_case(self, case_id)"}, {"doc": "Overwrite the free-text `notes` column for a case.\n\nUsed by the UI \"save/bookmark\" gesture. Kept here (rather than\nreaching into `_lock`/`_conn` from the route) so the store owns\nits own transaction boundary.", "kind": "method", "line": 280, "name": "set_notes", "signature": "def set_notes(self, case_id, notes)"}, {"kind": "method", "line": 291, "name": "get_case", "signature": "def get_case(self, case_id)"}, {"kind": "method", "line": 304, "name": "list_observations", "signature": "def list_observations(self, case_id)"}, {"kind": "method", "line": 327, "name": "list_entities", "signature": "def list_entities(self, case_id)"}, {"doc": "Compare two cases by entity (type, value) keys.\n\nReturns the symmetric difference plus counts and the per-type\nbreakdown. The \"added\" set is what case B learned that A did\nnot have; \"removed\" is the inverse. This is the OSINT analogue\nof `git diff A B` and is the building block for the\n\"what's new since last run\" UI the analyst wants to see.\n\nBoth cases are looked up in a single connection hold so the\ndiff is consistent even if the case store is being written\nto concurrently.", "kind": "method", "line": 342, "name": "diff_entities", "signature": "def diff_entities(self, case_a, case_b)"}, {"doc": "Lightweight case search. LIKE on `query` (not indexed, but\nacceptable for the OSINT scale of a few thousand cases).", "kind": "method", "line": 389, "name": "search_cases", "signature": "def search_cases(self, query_substring, limit, query_type)"}, {"doc": "Find every case that observed a given entity.\n\nThis is the cross-run memory query — the heart of \"have I\nseen this before?\".", "kind": "method", "line": 419, "name": "search_by_entity", "signature": "def search_by_entity(self, ent_type, value, limit)"}, {"kind": "method", "line": 447, "name": "stats", "signature": "def stats(self)"}, {"kind": "method", "line": 455, "name": "_row_to_case", "signature": "def _row_to_case(self, row)"}, {"kind": "method", "line": 481, "name": "_safe_json", "signature": "def _safe_json(text)"}, {"kind": "method", "line": 363, "name": "_per_type", "signature": "def _per_type(pairs)"}, {"kind": "method", "line": 369, "name": "_serialise", "signature": "def _serialise(pairs)"}]}, {"doc": "estorides_core.change_detection =============================== The temporal layer: \"what changed in this investigation since the last run?\". Takes two immutable :class:`Snapshot` objects and emits a :class:`ChangeReport` listing every new entity, every disappeared one, every property change, every new source corroboration, and every edge added/removed. Each :class:`Change` carries an auditable ``score`` reliability-weighted via :mod:`reliability_scoring`.  Pure module: no I/O, no DB writes, no logging of payload, no clock. Same input ⇒ same output bit-by-bit. Change ids are content hashes so the case store can deduplicate events across runs.  Public surface::  detect_changes(snapshot_before, snapshot_after, *, config=ChangeConfig()) -> ChangeReport", "id": "estorides_core/change_detection.py", "kind": "module", "label": "change_detection.py", "language": "py", "sha256": "812f0296610a0765", "symbol_count": 20, "symbols": [{"doc": "Outgoing edge: typed destination + relation name.", "kind": "class", "line": 59, "name": "Edge", "signature": "class Edge"}, {"doc": "One entity as captured at snapshot time.\n\nMirrors the fields the fusion store already persists\n(``first_seen``/``last_seen``/``confidence``/``sources``), so a\nSnapshot can be built directly from a fused row.", "kind": "class", "line": 67, "name": "SnapshotEntity", "signature": "class SnapshotEntity"}, {"doc": "An immutable view of an investigation at one point in time.", "kind": "class", "line": 97, "name": "Snapshot", "signature": "class Snapshot"}, {"doc": "Tuning for :func:`detect_changes`.", "kind": "class", "line": 105, "name": "ChangeConfig", "signature": "class ChangeConfig"}, {"doc": "Structured description of a single change's delta.", "kind": "class", "line": 126, "name": "Diff", "signature": "class Diff"}, {"doc": "One detected change.", "kind": "class", "line": 135, "name": "Change", "signature": "class Change"}, {"doc": "Aggregate stats for a :class:`ChangeReport`.", "kind": "class", "line": 152, "name": "ChangeSummary", "signature": "class ChangeSummary"}, {"doc": "Top-N changes + summary stats for a single diff operation.", "kind": "class", "line": 169, "name": "ChangeReport", "signature": "class ChangeReport"}, {"kind": "method", "line": 177, "name": "_truncate_key", "signature": "def _truncate_key(key)"}, {"kind": "method", "line": 184, "name": "_change_id", "signature": "def _change_id(kind, entity_id, diff_signature)"}, {"doc": "A=1, B=2, ..., F=6. For 'min_reliability' comparison.", "kind": "method", "line": 188, "name": "_reliability_floor", "signature": "def _reliability_floor(letter)"}, {"kind": "method", "line": 193, "name": "_filter_sources_by_reliability", "signature": "def _filter_sources_by_reliability(sources, min_reliability)"}, {"doc": "Compute the per-key add/change/remove between two property maps.", "kind": "method", "line": 206, "name": "_property_diff", "signature": "def _property_diff(before, after)"}, {"kind": "method", "line": 225, "name": "_edge_set", "signature": "def _edge_set(edges)"}, {"doc": "Sorted union of two entities' source lists.\n\nUsed in every per-entity Change (property_changed, source_added,\nsource_removed, edge_added, edge_removed, confidence_shifted) as\nthe ``sources`` field of the audit trail — the analyst should be\nable to see every source that touched the entity, not just the\ntrigger of the change.", "kind": "method", "line": 229, "name": "_union_sources", "signature": "def _union_sources(a, b)"}, {"doc": "Build a :class:`Change` with the deterministic id derived from\n``kind + entity_id + sig``. Centralised so the eight change kinds\ncan never disagree on the field set or the id format.", "kind": "method", "line": 241, "name": "_make_change", "signature": "def _make_change(kind, entity_id, entity_type, entity_value, sig)"}, {"doc": "True if a source's reliability is strictly *worse* than the\nconfigured minimum (i.e. it should be filtered out).", "kind": "method", "line": 274, "name": "_below_min_reliability", "signature": "def _below_min_reliability(source, min_reliability)"}, {"doc": "Diff two snapshots. Pure: no I/O, deterministic, bounded.\n\nParameters\n----------\nsnapshot_before, snapshot_after\n    The two snapshots to diff. Either may be ``None``:\n    ``before=None`` ⇒ first run, every entity is ``new``;\n    ``after=None`` ⇒ no future data, empty report.\nconfig\n    Optional tuning. See :class:`ChangeConfig`.", "kind": "method", "line": 284, "name": "detect_changes", "signature": "def detect_changes(snapshot_before, snapshot_after)"}, {"kind": "method", "line": 85, "name": "__post_init__", "signature": "def __post_init__(self)"}, {"kind": "method", "line": 116, "name": "__post_init__", "signature": "def __post_init__(self)"}]}, {"id": "estorides_core/cloud_asset_discovery.py", "kind": "module", "label": "cloud_asset_discovery.py", "language": "py", "sha256": "41af8fa0bccb272e", "symbol_count": 7, "symbols": [{"kind": "class", "line": 34, "name": "CloudAsset", "signature": "class CloudAsset"}, {"kind": "class", "line": 50, "name": "CloudAssetDiscoveryResult", "signature": "class CloudAssetDiscoveryResult"}, {"kind": "method", "line": 65, "name": "generate_bucket_names", "signature": "def generate_bucket_names(domain)"}, {"kind": "method", "line": 84, "name": "assess_bucket", "signature": "def assess_bucket(url, method)"}, {"kind": "class", "line": 93, "name": "CloudAssetDiscoveryError", "signature": "class CloudAssetDiscoveryError(Exception)"}, {"kind": "method", "line": 45, "name": "to_dict", "signature": "def to_dict(self)"}, {"kind": "method", "line": 56, "name": "to_dict", "signature": "def to_dict(self)"}]}, {"id": "estorides_core/code_exposure.py", "kind": "module", "label": "code_exposure.py", "language": "py", "sha256": "5c9e3c009e32f020", "symbol_count": 10, "symbols": [{"kind": "class", "line": 47, "name": "CodeFinding", "signature": "class CodeFinding"}, {"kind": "class", "line": 62, "name": "SeveritySummary", "signature": "class SeveritySummary"}, {"kind": "class", "line": 74, "name": "CodeExposureResult", "signature": "class CodeExposureResult"}, {"kind": "method", "line": 91, "name": "validate_aws_key", "signature": "def validate_aws_key(key)"}, {"kind": "method", "line": 95, "name": "_is_placeholder", "signature": "def _is_placeholder(text)"}, {"kind": "method", "line": 99, "name": "classify_finding", "signature": "def classify_finding(content, source, file_path)"}, {"kind": "method", "line": 189, "name": "analyse_findings", "signature": "def analyse_findings(findings, rate_limited)"}, {"kind": "method", "line": 57, "name": "to_dict", "signature": "def to_dict(self)"}, {"kind": "method", "line": 69, "name": "to_dict", "signature": "def to_dict(self)"}, {"kind": "method", "line": 81, "name": "to_dict", "signature": "def to_dict(self)"}]}, {"doc": "estorides.config ================ Central configuration. Everything tunable lives here.  Two layers coexist on purpose:  * Module-level constants (the original surface). Kept verbatim so the rest of the engine keeps importing `from .config import X` unchanged. * Frozen dataclass config objects (the structured surface) for the v1.3 subsystems: response cache TTL, the recursive pivot engine, the SSE streaming layer and the web tunables. These group related knobs, validate their own bounds at construction and expose a single immutable instance each. No subsystem hard-codes a number; it reads its field off the relevant config object.  Every tunable resolves from an environment variable with a typed, fault-tolerant reader (a malformed env var falls back to the default and logs, instead of crashing the whole process at import time).", "id": "estorides_core/config.py", "kind": "module", "label": "config.py", "language": "py", "sha256": "facebef0db66e63b", "symbol_count": 29, "symbols": [{"doc": "`<prefix>/share/estorides` — the data-files target of a pip install.", "kind": "function", "line": 42, "name": "_installed_share", "signature": "def _installed_share()"}, {"doc": "Resolve a read-only runtime data dir.\n\nPrecedence: env var > dev checkout (`PROJECT_ROOT/<dirname>`) >\npip-installed prefix (`<prefix>/share/estorides/<dirname>`, the\n`[tool.setuptools.data-files]` target). No I/O besides `is_dir`.", "kind": "function", "line": 47, "name": "_resolve_dir", "signature": "def _resolve_dir(env_key, dirname)"}, {"doc": "Resolve a writable runtime dir (no import-time mkdir, issue #49).\n\nPrecedence: env var > dev checkout (`PROJECT_ROOT/<dirname>`, when it\nexists) > `$XDG_DATA_HOME/estorides/<dirname>` (default\n`~/.local/share/estorides/<dirname>`). A pip-installed wheel must\nnever write into site-packages.", "kind": "function", "line": 63, "name": "_resolve_writable_dir", "signature": "def _resolve_writable_dir(env_key, dirname)"}, {"doc": "Idempotently create DATA_DIR.\n\nReplaces the import-time mkdir that issue #49 reported. Call\nthis from the app factory and from any code path that actually\nneeds to write to DATA_DIR. Idempotent: exist_ok=True so a\npre-existing directory is fine.", "kind": "function", "line": 101, "name": "ensure_data_dirs", "signature": "def ensure_data_dirs()"}, {"doc": "Idempotently create REPORTS_DIR.\n\nSame posture as ensure_data_dirs(). /api/export/<fmt> used to\nwrite through this path; issue #43 moved export artefacts to a\ntempfile, but the directory is still useful for operator-facing\nexports.", "kind": "function", "line": 112, "name": "ensure_reports_dir", "signature": "def ensure_reports_dir()"}, {"kind": "function", "line": 169, "name": "_env_tool_allowlist", "signature": "def _env_tool_allowlist()"}, {"doc": "Map a contact class to its numeric severity, unknown values to active.\n\nAn unrecognised class is treated as the most exposing (`active`) so a\ntypo in a source YAML can never silently downgrade an operator's\npassive-only guarantee.", "kind": "function", "line": 247, "name": "contact_level", "signature": "def contact_level(contact)"}, {"doc": "Resolve the proxy rotation pool from an explicit value or the env.\n\nPrecedence: an explicit caller value (CLI flag) wins; otherwise the\npool env var, otherwise the single-proxy env var. Returns an empty\nlist when no anonymising egress is configured.", "kind": "function", "line": 273, "name": "effective_proxies", "signature": "def effective_proxies(explicit)"}, {"doc": "Disk response-cache behaviour.", "kind": "class", "line": 407, "name": "CacheConfig", "signature": "class CacheConfig"}, {"doc": "Which entity types are worth pivoting on, and how leads are scored.\n\nThe recursive cross-search expands the highest-scoring leads first.\n`type_weights` lets a high-signal selector (an email, a wallet) outrank\na low-signal one (a shared CDN IP) at the same depth. `depth_decay`\ndiscounts every additional hop so the frontier stays close to the seed.", "kind": "class", "line": 420, "name": "PivotPolicyConfig", "signature": "class PivotPolicyConfig"}, {"doc": "Bounds and defaults for the recursive pivot engine.\n\n`*_cap` values are the absolute ceilings applied to any caller-supplied\noverride (e.g. an API request body), so an untrusted client can never\nrequest an unbounded crawl.", "kind": "class", "line": 445, "name": "PivotConfig", "signature": "class PivotConfig"}, {"doc": "Server-Sent-Events streaming knobs (buffer size, cadence).", "kind": "class", "line": 490, "name": "StreamConfig", "signature": "class StreamConfig"}, {"doc": "Centralised tunables for the passive recon fusion engine.\n\nControls how raw OSINT results are grouped, deduplicated and classified\ninto relevance tiers. Every field has an env var equivalent so the\noperator can adjust behaviour without touching code.\n\nThis is the SINGLE source of truth. The engine module imports from here\nand never defines its own copy.", "kind": "class", "line": 502, "name": "ReconFusionConfig", "signature": "class ReconFusionConfig"}, {"doc": "Bounds and version for the strict observation/entity data contracts.\n\n`max_str_len` caps any free-form string field (source, category, …).\n`max_value_len` caps the identity-bearing entity `value`: it is tighter\nthan `max_str_len` because a `value` is the merge/identity key used to\nderive `canonical_id` downstream, so an oversized blob must be rejected,\nnot truncated. `max_url_len` is applied as a truncation bound on `meta.url`\n(a URL is not identity-bearing; truncating it is a documented, safe bound).\n`schema_version` lets consumers detect a contract drift.", "kind": "class", "line": 534, "name": "SchemaConfig", "signature": "class SchemaConfig"}, {"doc": "Per-endpoint defaults and render limits for the Flask layer.", "kind": "class", "line": 561, "name": "WebConfig", "signature": "class WebConfig"}, {"doc": "Central retry bounds for HTTP fanout.", "kind": "class", "line": 576, "name": "RetryConfig", "signature": "class RetryConfig"}, {"doc": "Default per-type lead weights for the pivot scorer.\n\nStrong, single-owner selectors rank above shared infrastructure.", "kind": "method", "line": 591, "name": "_pivot_weight_map", "signature": "def _pivot_weight_map()"}, {"doc": "Read a comma-separated env var into a frozenset, else the default.", "kind": "method", "line": 608, "name": "_csv_frozenset", "signature": "def _csv_frozenset(name, default)"}, {"doc": "Return the backoff sleep for a 1-indexed attempt.", "kind": "method", "line": 713, "name": "retry_delay", "signature": "def retry_delay(attempt)"}, {"doc": "Cache is only consulted when enabled and the TTL is positive.", "kind": "method", "line": 414, "name": "is_active", "signature": "def is_active(self)"}, {"doc": "True when an entity of `entity_type` should be re-queried.", "kind": "method", "line": 434, "name": "is_pivotable", "signature": "def is_pivotable(self, entity_type)"}, {"doc": "Priority of expanding this lead. Higher expands sooner.", "kind": "method", "line": 438, "name": "lead_score", "signature": "def lead_score(self, entity_type, depth, parent_score)"}, {"doc": "Clamp a requested depth into [1, max_depth_cap].", "kind": "method", "line": 468, "name": "clamp_depth", "signature": "def clamp_depth(self, value)"}, {"doc": "Clamp a requested step budget into [1, max_steps_cap].", "kind": "method", "line": 472, "name": "clamp_steps", "signature": "def clamp_steps(self, value)"}, {"doc": "Clamp a requested entity budget into [1, max_entities_cap].", "kind": "method", "line": 476, "name": "clamp_entities", "signature": "def clamp_entities(self, value)"}, {"doc": "Clamp a requested fan-out width into [1, parallel_cap].", "kind": "method", "line": 480, "name": "clamp_parallel", "signature": "def clamp_parallel(self, value)"}, {"doc": "Clamp a requested per-target deadline into (0, deadline_cap_seconds].", "kind": "method", "line": 484, "name": "clamp_deadline", "signature": "def clamp_deadline(self, value)"}, {"kind": "method", "line": 523, "name": "__post_init__", "signature": "def __post_init__(self)"}, {"doc": "Exponential backoff for 1-indexed attempt, clamped to cap.", "kind": "method", "line": 584, "name": "delay", "signature": "def delay(self, attempt)"}]}, {"doc": "Background subdomain/domain discoverer.  Drops a seed and walks the passive attack surface recursively. As of v1.3 the recursion itself lives in `pivot_engine.PivotEngine`; this module is the thin adapter that:  * owns the long-lived `DiscoverJob` registry the SSE/CLI layers poll, * drives the engine with the infrastructure-only pivot policy (domains and IPs, the historical discoverer surface), and * translates engine `PivotEvent`s into the legacy event dicts the UI and CLI already understand (`started`, `step_start`, `node_found`, `step_done`, `finished`, `error`, ...).  Keeping one engine and many adapters is the DRY win: the deep-run SSE endpoint and this background discoverer share identical recursion, scoring and budgeting code; only the policy and the event shape differ.  Design guarantees preserved from v1.2:  * No new HTTP layer — the engine reuses the existing `Orchestrator`. * Persistence is via the case store; every hop appends to one case. * Bounded: depth, steps and entity ceilings come from config caps. * Idempotent: re-queuing the same (type, value) is a no-op (engine).", "id": "estorides_core/discoverer.py", "kind": "module", "label": "discoverer.py", "language": "py", "sha256": "62852caff1de5c97", "symbol_count": 21, "symbols": [{"doc": "One background discovery session.\n\nLives in `DISCOVER_JOBS` keyed by job_id; survives the SSE handler\nexiting (so a UI reload reconnects and resumes).", "kind": "class", "line": 50, "name": "DiscoverJob", "signature": "class DiscoverJob"}, {"doc": "Adapts engine `PivotEvent`s to the legacy DiscoverJob event dicts.\n\nThe UI and CLI consume `step_start`, `node_found`, `step_done`,\n`finished` and `error`; this translator preserves those shapes so the\nengine swap is invisible to every existing consumer.", "kind": "class", "line": 95, "name": "_DiscoverJobSink", "signature": "class _DiscoverJobSink"}, {"doc": "Monotonic-ish id with a timestamp prefix for natural sort.", "kind": "method", "line": 189, "name": "_new_job_id", "signature": "def _new_job_id()"}, {"doc": "Create and register a discovery job synchronously.\n\nThis does only fast, loop-free work (a case-store insert and some\nbookkeeping), so it is safe to call straight from a Flask request\nthread. The asyncio worker that actually crawls is scheduled\nseparately by `start_discover` / `start_discover_threadsafe`, so the\ncaller never blocks on the shared background loop.", "kind": "method", "line": 194, "name": "create_discover_job", "signature": "def create_discover_job(seed_type, seed_value)"}, {"doc": "Create a discovery job and schedule its worker on the current loop.\n\nKept as the coroutine entry point for callers that already own a\nrunning loop (the CLI). Web callers use `start_discover_threadsafe`,\nwhich never blocks the request thread on the background loop.", "kind": "method", "line": 252, "name": "start_discover", "signature": "def start_discover(seed_type, seed_value)"}, {"doc": "Create the job in the calling thread, fire its worker on `loop`.\n\nReturns immediately. The worker is queued with\n`run_coroutine_threadsafe` and runs whenever the loop is next free, so\na busy loop (a concurrent deep-run) can never make this call time out.", "kind": "method", "line": 285, "name": "start_discover_threadsafe", "signature": "def start_discover_threadsafe(loop, seed_type, seed_value)"}, {"doc": "The background loop. One asyncio task per job, driving the engine.", "kind": "method", "line": 319, "name": "_run_discoverer", "signature": "def _run_discoverer(job)"}, {"doc": "Snapshot of the recent jobs for the /api/discover/jobs endpoint.", "kind": "method", "line": 349, "name": "list_jobs", "signature": "def list_jobs(limit)"}, {"kind": "method", "line": 78, "name": "stop", "signature": "def stop(self)"}, {"kind": "method", "line": 81, "name": "should_stop", "signature": "def should_stop(self)"}, {"doc": "Append an event and keep the buffer bounded.", "kind": "method", "line": 84, "name": "push_event", "signature": "def push_event(self, ev)"}, {"kind": "method", "line": 103, "name": "__init__", "signature": "def __init__(self, job)"}, {"kind": "method", "line": 106, "name": "emit", "signature": "def emit(self, event)"}, {"kind": "method", "line": 113, "name": "_on_started", "signature": "def _on_started(self, data)"}, {"kind": "method", "line": 116, "name": "_on_target_start", "signature": "def _on_target_start(self, data)"}, {"kind": "method", "line": 126, "name": "_on_entity", "signature": "def _on_entity(self, data)"}, {"kind": "method", "line": 141, "name": "_on_target_done", "signature": "def _on_target_done(self, data)"}, {"kind": "method", "line": 153, "name": "_on_target_error", "signature": "def _on_target_error(self, data)"}, {"kind": "method", "line": 160, "name": "_on_stopping", "signature": "def _on_stopping(self, data)"}, {"kind": "method", "line": 163, "name": "_on_finished", "signature": "def _on_finished(self, data)"}, {"kind": "method", "line": 174, "name": "_on_fatal", "signature": "def _on_fatal(self, data)"}]}, {"doc": "estorides_core.entity_extraction ================================ Single source of truth for finding entities (IPs, domains, emails, CVEs, crypto addresses, etc.) anywhere in any JSON / text response.  This is the function that turns a wall of OSINT JSON into nodes the knowledge graph can wire up.", "id": "estorides_core/entity_extraction.py", "kind": "module", "label": "entity_extraction.py", "language": "py", "sha256": "ece314f15d0b476f", "symbol_count": 21, "symbols": [{"kind": "class", "line": 22, "name": "Entity", "signature": "class Entity"}, {"doc": "True for E.164-ish numbers with 7-15 digits (never consumes hashes).", "kind": "method", "line": 49, "name": "_looks_like_phone", "signature": "def _looks_like_phone(query)"}, {"doc": "Return the routing form of raw operator input.\n\nURLs reduce to their lowercased host (userinfo, port, path, query\nand fragment stripped); `@handles` reduce to the bare handle;\neverything else is trimmed verbatim.", "kind": "method", "line": 57, "name": "normalize_query", "signature": "def normalize_query(query)"}, {"doc": "Return the detected type of a free-form query.\n\nFalls back to 'domain' for anything that looks like a hostname\n(contains a dot, no spaces), and to 'keyword' for everything else.", "kind": "method", "line": 100, "name": "detect_query_type", "signature": "def detect_query_type(query)"}, {"kind": "method", "line": 127, "name": "_is_valid_domain", "signature": "def _is_valid_domain(candidate)"}, {"kind": "method", "line": 140, "name": "_context", "signature": "def _context(text, start, end, window)"}, {"doc": "Find every recognised entity in a raw text blob.\n\n`types` optionally restricts which kinds of entity to look for.", "kind": "method", "line": 146, "name": "extract_from_text", "signature": "def extract_from_text(text, source)"}, {"doc": "Heuristic: only count a numeric match as an IP if it isn't part of a\nversion number or timestamp (preceded/followed by `version`, `v`, or `:`).", "kind": "method", "line": 200, "name": "_ip_in_textual_context", "signature": "def _ip_in_textual_context(text, idx)"}, {"doc": "Pull entities out of a JSON-like structure.\n\nEarlier this recursed into every string and ran all patterns on each one,\nso a response with thousands of entries (crt.sh subdomains, wayback URLs)\ntriggered hundreds of thousands of regex passes. We now flatten the payload\nto a single capped string and scan it once; entities span keys and values\njust as well, and the cost is bounded by `extract_from_text`.", "kind": "method", "line": 210, "name": "extract_from_json", "signature": "def extract_from_json(payload, source)"}, {"doc": "Return a stripped string for a scalar leaf, or None for non-scalars.", "kind": "method", "line": 278, "name": "_clean_scalar", "signature": "def _clean_scalar(value)"}, {"doc": "True when a value reads like a human name (has a space, mostly letters).", "kind": "method", "line": 288, "name": "_looks_like_person", "signature": "def _looks_like_person(value)"}, {"doc": "True when a value reads like a handle (no spaces, handle charset).", "kind": "method", "line": 299, "name": "_looks_like_username", "signature": "def _looks_like_username(value)"}, {"doc": "Map a (key, scalar value) pair to a human-selector entity type, or None.", "kind": "method", "line": 306, "name": "_classify_keyed_value", "signature": "def _classify_keyed_value(key, value)"}, {"doc": "Extract human selectors (email, username, person, org, phone) by key.\n\nWalks the JSON structure and types values by the key they sit under,\nwhich is the only reliable way to recover usernames and person names\n(they have no lexical signature for a regex to catch). Bounded by\n`ENTITY_MAX_PER_TYPE` per type and a node-visit cap so a pathological\nresponse cannot turn this into a CPU stall.", "kind": "method", "line": 330, "name": "extract_structured", "signature": "def extract_structured(payload, source)"}, {"doc": "Deduplicate by (type, value) and merge sources / contexts.\n\nTwo entities with the same (type, value) collapse into a single\nrecord. Their `sources` lists are unioned (preserving the first\nseen order), `context` is concatenated up to a 200-char window,\nand `confidence` is bumped by 0.1 for each extra observation\n(capped at 1.0) — a simple \"corroboration bonus\".\n\nThe `sources` field is the canonical \"all the places this was\nseen\" record. The previous implementation stored it in a\nprivate attribute (`_src`) that was never serialised and broke\n`asdict()` (TypeError on `dataclasses.asdict` for non-dataclass\nattributes). This version is a real, declared field.\n\nv1.1: After exact-key dedup, run a second pass with\n`difflib.SequenceMatcher` to catch near-misses like\n`EvilCorp.com` vs `evil-corp.com`. Anything above the\n`FUZZY_THRESHOLD` ratio collapses into a single record.\nReturns the deduped list, with cluster groups available via\n`fuzzy_clusters` if the caller wants them.\n\nv1.4: pass ``fuzzy=False`` to do exact-key dedup only. The\norchestrator does this when the canonical identity layer\n(`entity_resolution`) is active, so the resolver owns every fuzzy\ndecision instead of inheriting a coarser difflib pre-merge that would,\nfor example, silently fuse two distinct domain registrations the\nresolver intends to keep separate and merely *link*.", "kind": "method", "line": 382, "name": "merge", "signature": "def merge()"}, {"doc": "Group entities of the same type by string similarity and merge.\n\nUses `difflib.SequenceMatcher.ratio()`. We compare normalised\n(lowercased, hyphen-stripped) forms so `evil-corp.com` and\n`evilcorp.com` collide cleanly. Only domain / email / person /\norg types are eligible — IPs, hashes, and CVEs have exact\nsemantics where fuzzy would be a bug, not a feature.", "kind": "method", "line": 476, "name": "_fuzzy_cluster", "signature": "def _fuzzy_cluster(entities)"}, {"kind": "method", "line": 34, "name": "to_dict", "signature": "def to_dict(self)"}, {"kind": "method", "line": 344, "name": "visit", "signature": "def visit(node, key)"}, {"kind": "method", "line": 499, "name": "find", "signature": "def find(x, parent)"}, {"kind": "method", "line": 505, "name": "union", "signature": "def union(a, b, parent)"}, {"kind": "method", "line": 510, "name": "norm", "signature": "def norm(v)"}]}, {"doc": "estorides_core.entity_resolution ================================ Canonical identity layer.  The v1.1 engine collapsed near-duplicate entities with a single ``difflib.SequenceMatcher`` ratio at 0.85. That is fine for typo folding but it is not entity resolution: it has no stable identifier, no blocking (so it is quietly O(n^2) over the whole entity list), no notion of which match was deterministic versus a guess, and no cross-script awareness, so ``Владимир Путин`` and ``Vladimir Putin`` stay two unrelated nodes.  This module is the replacement. For a per-run list of :class:`Entity` records it produces a list of :class:`CanonicalEntity` records, each with:  * a **stable canonical id** derived from a type-aware normalised form, so the same real-world entity gets the same id on every run (and, with the persistent store, across runs); * **blocked, scored matching** — exact normalised equality is a deterministic merge; close-but-not-equal pairs inside the same blocking bucket are scored with Jaro-Winkler (and a cross-script consonant skeleton booster) and either merged or surfaced as a ``SAME_AS`` candidate link, never silently fused; * **provenance** — the sources, original surface forms (aliases), scripts, and the match method/score that justified each merge.  Deterministic types (IP, hash, CVE, ASN, crypto address) never fuzzy match: for those, equality of the normalised form is the only merge rule, because a one-character difference is a different host, file, or wallet.", "id": "estorides_core/entity_resolution.py", "kind": "module", "label": "entity_resolution.py", "language": "py", "sha256": "4ac25c44be6f1d69", "symbol_count": 35, "symbols": [{"doc": "Return the Jaro similarity of two strings in ``[0, 1]``.", "kind": "function", "line": 83, "name": "jaro", "signature": "def jaro(s1, s2)"}, {"doc": "Jaro-Winkler similarity: Jaro with a shared-prefix bonus.\n\nThe prefix bonus (up to 4 leading characters) rewards strings that\nagree at the start, which is the common shape of name and domain\nvariants. ``prefix_weight`` is capped at 0.25 to keep the result in\n``[0, 1]``.", "kind": "function", "line": 126, "name": "jaro_winkler", "signature": "def jaro_winkler(s1, s2, prefix_weight)"}, {"doc": "Return a 4-character Soundex code for a Latin token.\n\nUsed only as a blocking key: it groups phonetically similar tokens so\nthe expensive pairwise scorer runs on plausible candidates rather than\nthe whole list. Empty or non-alphabetic input yields ``\"0000\"``.", "kind": "function", "line": 146, "name": "_soundex", "signature": "def _soundex(token)"}, {"kind": "function", "line": 178, "name": "_normalize_domain", "signature": "def _normalize_domain(value)"}, {"doc": "Order-independent transliterated key for persons and orgs.\n\nThe surface form is transliterated to Latin, punctuation dropped, and\nthe tokens sorted, so ``\"Putin, Vladimir\"`` and ``\"Владимир Путин\"``\nconverge on the same normalised string and merge deterministically.\nCross-script spellings that survive transliteration with different\nvowels (abjad scripts) are caught later by the skeleton scorer.", "kind": "function", "line": 187, "name": "_normalize_name", "signature": "def _normalize_name(value)"}, {"doc": "Return the canonical normalised form of an entity value.\n\nThe normalised form is the merge key for deterministic types and the\nseed of the stable canonical id for every type. It is intentionally\nlossy (case, ordering, ornament removed) but never collapses distinct\nobjects of a deterministic type.", "kind": "function", "line": 206, "name": "normalize_value", "signature": "def normalize_value(etype, value)"}, {"doc": "Stable, content-addressed id for a normalised entity.\n\nSame ``(type, normalized)`` always yields the same id, so a canonical\nentity keeps its identity for as long as its normalised form is stable.\nThe persistent store additionally maps known aliases onto an existing\nid so a never-before-seen surface form still resolves to the same node.", "kind": "function", "line": 246, "name": "canonical_id", "signature": "def canonical_id(etype, normalized)"}, {"doc": "Return the blocking keys that bucket an entity for comparison.\n\nTwo entities are only ever scored against each other if they share at\nleast one blocking key. The keys are chosen to be high-recall (so true\nmatches land together) while keeping buckets small enough that the\nin-bucket pairwise scan stays cheap.", "kind": "function", "line": 257, "name": "blocking_keys", "signature": "def blocking_keys(etype, normalized, value)"}, {"doc": "The result of comparing two entity values of the same type.", "kind": "class", "line": 295, "name": "MatchScore", "signature": "class MatchScore"}, {"doc": "Score how likely two same-type entities denote the same object.\n\nDeterministic types return 1.0 only on normalised equality and 0.0\notherwise. Fuzzy types layer Jaro-Winkler over the normalised form with\na cross-script consonant-skeleton booster for names, so a Cyrillic and\na Latin spelling of one person can clear the link bar even when their\ntransliterated vowels differ.", "kind": "method", "line": 302, "name": "score_pair", "signature": "def score_pair(etype, a_value, b_value, a_norm, b_norm)"}, {"doc": "A resolved identity fused from one or more observed entities.", "kind": "class", "line": 341, "name": "CanonicalEntity", "signature": "class CanonicalEntity"}, {"doc": "A suggested-but-unmerged identity link between two canonical ids.", "kind": "class", "line": 401, "name": "SameAsLink", "signature": "class SameAsLink"}, {"doc": "Output of a resolve() call: fused entities plus candidate links.", "kind": "class", "line": 419, "name": "ResolutionResult", "signature": "class ResolutionResult"}, {"doc": "Disjoint-set over integer indices with path compression.", "kind": "class", "line": 432, "name": "_UnionFind", "signature": "class _UnionFind"}, {"kind": "method", "line": 450, "name": "_script_of", "signature": "def _script_of(value)"}, {"doc": "Resolve a per-run entity list into canonical identities.\n\nThe resolver is deliberately stateless across calls by default; pass a\nstore (see :mod:`estorides_core.entity_store`) to make canonical ids\nstable across runs. A single :meth:`resolve` call:\n\n1. exact-merges entities sharing a ``(type, normalized)`` key;\n2. blocks the remaining fuzzy-eligible groups and scores in-bucket\n   pairs, unioning clusters at or above the merge threshold and\n   recording ``SAME_AS`` candidates above the link threshold;\n3. emits one :class:`CanonicalEntity` per cluster with a deterministic\n   representative and full provenance.", "kind": "class", "line": 454, "name": "EntityResolver", "signature": "class EntityResolver"}, {"doc": "Module-level convenience wrapper around :class:`EntityResolver`.", "kind": "method", "line": 727, "name": "resolve_entities", "signature": "def resolve_entities(entities)"}, {"kind": "method", "line": 357, "name": "to_dict", "signature": "def to_dict(self)"}, {"doc": "Project back onto the legacy :class:`Entity` shape.\n\nLets the resolver drop into the existing orchestrator, knowledge\ngraph, and case-store paths without changing their interfaces while\ncarrying the new identity metadata in ``attributes``.", "kind": "method", "line": 373, "name": "to_entity", "signature": "def to_entity(self)"}, {"kind": "method", "line": 409, "name": "to_dict", "signature": "def to_dict(self)"}, {"kind": "method", "line": 425, "name": "to_dict", "signature": "def to_dict(self)"}, {"kind": "method", "line": 435, "name": "__init__", "signature": "def __init__(self, n)"}, {"kind": "method", "line": 438, "name": "find", "signature": "def find(self, x)"}, {"kind": "method", "line": 444, "name": "union", "signature": "def union(self, a, b)"}, {"kind": "method", "line": 469, "name": "__init__", "signature": "def __init__(self)"}, {"doc": "Resolve ``entities`` into canonical identities and links.", "kind": "method", "line": 482, "name": "resolve", "signature": "def resolve(self, entities)"}, {"kind": "method", "line": 498, "name": "_build_records", "signature": "def _build_records(self, entities)"}, {"kind": "method", "line": 513, "name": "_exact_merge", "signature": "def _exact_merge(records, uf)"}, {"doc": "Block, score, merge at threshold, and collect link candidates.\n\nMerges (union) happen eagerly as pairs clear the merge bar. Pairs\nthat only clear the lower link bar are returned as ``(a, b, score)``\nindex triples for the caller to translate into canonical-id links\nonce clusters are materialised.", "kind": "method", "line": 525, "name": "_fuzzy_merge", "signature": "def _fuzzy_merge(self, records, uf)"}, {"doc": "Translate index-pair link candidates into canonical-id links.\n\nPairs whose clusters ended up merged (a later, stronger edge pulled\nthem together) are dropped; the rest are deduplicated per\ncanonical-id pair, keeping the highest-scoring justification.", "kind": "method", "line": 566, "name": "_build_links", "signature": "def _build_links(candidates, uf, root_to_cid)"}, {"kind": "method", "line": 594, "name": "_materialise", "signature": "def _materialise(self, records, uf)"}, {"doc": "Pick a stable representative for a cluster.\n\nPreference order: most corroborated (appears in most sources), then\na Latin surface form over a non-Latin one (more broadly readable in\nreports), then the lexicographically smallest value. The result is\ndeterministic for a given member set, which keeps the canonical id\nstable run to run.", "kind": "method", "line": 661, "name": "_representative", "signature": "def _representative(members)"}, {"doc": "Return the (method, score) of the strongest non-exact pair.\n\nSurfaces *why* a multi-member cluster was fused, so the analyst can\nsee whether the merge rested on an exact key or a probabilistic\nname match. Bounded to the first handful of members to stay cheap on\nlarge clusters.", "kind": "method", "line": 679, "name": "_best_internal_match", "signature": "def _best_internal_match(members)"}, {"doc": "Map canonicals onto persisted ids and record their aliases.\n\nLooked up alias-first so a brand-new surface form of a known entity\nadopts the existing canonical id instead of minting a new one. The\nstore call is best-effort: any failure leaves the freshly computed\nid in place rather than aborting the run.", "kind": "method", "line": 705, "name": "_reconcile_with_store", "signature": "def _reconcile_with_store(self, canonicals)"}, {"kind": "method", "line": 670, "name": "rank", "signature": "def rank(rec)"}]}, {"doc": "estorides_core.entity_store =========================== Cross-run canonical identity store.  The resolver in :mod:`estorides_core.entity_resolution` produces stable canonical ids *within* a run from content-addressed normalised forms. That is not enough for state-level work, where the same target recurs across investigations: a person seen as ``Vladimir Putin`` in one case and only as ``Владимир Путин`` in the next must keep one identifier so the graph, watchlists, and case history all join on it.  This store closes that gap. It persists every canonical entity and *all* the surface forms (aliases) ever observed for it, each indexed by its normalised key. On a later run the resolver asks :meth:`lookup` whether any of a freshly computed entity's normalised forms — its own or any of its aliases — has been seen before, and if so adopts the established canonical id instead of minting a new one.  It is a thin SQLite layer, matching :mod:`estorides_core.cases`: WAL mode, a single serialised connection behind a lock, and a fail-soft contract — the resolver treats any store error as \"no prior knowledge\" and proceeds, so a read-only or missing data directory degrades to in-run resolution rather than breaking a run.", "id": "estorides_core/entity_store.py", "kind": "module", "label": "entity_store.py", "language": "py", "sha256": "b7e12bb4fdf6ad8b", "symbol_count": 5, "symbols": [{"doc": "Thread-safe SQLite repository of canonical identities and aliases.", "kind": "class", "line": 60, "name": "EntityStore", "signature": "class EntityStore(SqliteStore)"}, {"doc": "Open the store, returning None instead of raising on failure.\n\nThe resolver calls this so that an unwritable or locked data directory\ndegrades the cross-run identity feature to in-run-only resolution rather\nthan aborting an investigation.", "kind": "method", "line": 155, "name": "open_store", "signature": "def open_store(path)"}, {"doc": "Return an existing canonical id for any known form, or None.\n\nChecks every normalised key the entity can present — its own and\neach alias's — against both the canonical ``entities`` table and the\n``aliases`` table. The first established id wins, so a never-before\nspelling of a known target still resolves to its prior identity.", "kind": "method", "line": 66, "name": "lookup", "signature": "def lookup(self, etype, normalized, aliases)"}, {"doc": "Persist (insert or update) a canonical entity and its aliases.\n\n``first_seen`` is preserved across updates; ``last_seen``,\n``member_count``, ``source_count``, ``value``, and ``confidence``\ntrack the latest resolution. Every alias is recorded with its\nnormalised key so future lookups can route any surface form back to\nthis id.", "kind": "method", "line": 104, "name": "upsert", "signature": "def upsert(self, entity)"}, {"doc": "Return a one-glance summary of store size.", "kind": "method", "line": 143, "name": "stats", "signature": "def stats(self)"}]}, {"doc": "estorides_core.event_bus ======================== In process publish subscribe channel for orchestrator lifecycle events.  I let metrics, SSE and future plugins observe runs without the orchestrator importing them. Delivery is synchronous and total: a failing subscriber never breaks a run and never mutates the payload seen by the next subscriber.", "id": "estorides_core/event_bus.py", "kind": "module", "label": "event_bus.py", "language": "py", "sha256": "895f990c17317173", "symbol_count": 9, "symbols": [{"doc": "Synchronous in memory event channel with isolated delivery.", "kind": "class", "line": 24, "name": "EventBus", "signature": "class EventBus"}, {"doc": "Return the process wide shared bus instance.", "kind": "method", "line": 85, "name": "get_bus", "signature": "def get_bus()"}, {"doc": "Create an empty bus with no subscribers.", "kind": "method", "line": 27, "name": "__init__", "signature": "def __init__(self)"}, {"doc": "Register handler for event and return it for unsubscription.", "kind": "method", "line": 31, "name": "subscribe", "signature": "def subscribe(self, event, handler)"}, {"doc": "Remove handler from event and report whether it was present.", "kind": "method", "line": 41, "name": "unsubscribe", "signature": "def unsubscribe(self, event, handler)"}, {"doc": "Deliver a copy of payload to each subscriber and count attempts.", "kind": "method", "line": 50, "name": "publish", "signature": "def publish(self, event, payload)"}, {"doc": "Remove subscribers for one event or for the whole bus.", "kind": "method", "line": 63, "name": "clear", "signature": "def clear(self, event)"}, {"doc": "Return the number of handlers registered for event.", "kind": "method", "line": 71, "name": "subscriber_count", "signature": "def subscriber_count(self, event)"}, {"doc": "Validate that an event name uses the constrained alphabet.", "kind": "method", "line": 76, "name": "_check_event", "signature": "def _check_event(self, event)"}]}, {"doc": "estorides_core.feeds ==================== Real-time intelligence feeds, polled and cached locally.  This is the \"osiris map layers\" feature, brought to the Python side: each feed is a small object that knows how to fetch, parse, and normalise one external signal (flights, earthquakes, fires, news, sanctions) into a common GeoPoint shape ready for the map.  Why a separate module instead of just YAML sources? * The feeds are polled, not queried. They don't take a user query; they take a bounding box or a time window. * They produce spatiotemporal data (lat, lon, timestamp) which doesn't fit the source-parser model. * They have a much higher refresh rate (15-60 min) so we cache aggressively to disk and short-circuit on the same minute.  Each feed exposes a uniform API: * `name`  — slug used by the API * `description` — human-readable one-liner * `fetch(bbox=None)` — list[dict] of normalised records * `point(record)` — (lat, lon) tuple, or None  Records are plain dicts with at least: {lat, lon, timestamp, label, source}.", "id": "estorides_core/feeds.py", "kind": "module", "label": "feeds.py", "language": "py", "sha256": "f938e29d5eeee1c1", "symbol_count": 16, "symbols": [{"doc": "Normalised point for the map layer.\n\n`kind` discriminates the marker on the map (flight / quake / fire /\nnews / vessel). `extra` is an open dict for source-specific fields\nthe frontend may want to render in the popup.", "kind": "class", "line": 54, "name": "FeedPoint", "signature": "class FeedPoint"}, {"doc": "Base class for a real-time feed.", "kind": "class", "line": 74, "name": "Feed", "signature": "class Feed(ABC)"}, {"doc": "USGS M2.5+ earthquakes, last 24h, worldwide.", "kind": "class", "line": 148, "name": "EarthquakesFeed", "signature": "class EarthquakesFeed(Feed)"}, {"doc": "NASA FIRMS active fire hotspots (VIIRS_NOAA20_NRT, last 24h).\n\nFIRMS retired its keyless CSV download in 2024. The new endpoint\nat `api/data/active_fire/csv/...` requires a MAP_KEY (free with\nNASA Earthdata registration) which is read from the\n`ESTORIDES_FIRMS_KEY` env var. If the key is missing the feed\nsilently returns zero points and logs a warning — it never\nbreaks the rest of the platform.", "kind": "class", "line": 190, "name": "FiresFeed", "signature": "class FiresFeed(Feed)"}, {"doc": "GDELT 2.0 — global news articles (article-list API).\n\nThe GeoJSON endpoint we used in the previous version was retired.\nThe current keyless endpoint is the article-list API at\n`/api/v2/doc/doc` which returns a JSON `articles` array. We\nattempt to geocode the article URL via the embedded\n`socialimage` or `domain` field when coordinates aren't in the\npayload — falling back to the article's source domain\ncoordinates is intentionally out of scope for this version, so\na record without explicit coordinates is dropped.", "kind": "class", "line": 251, "name": "NewsFeed", "signature": "class NewsFeed(Feed)"}, {"doc": "Return public feed descriptions for the /api/feeds endpoint.", "kind": "method", "line": 313, "name": "list_feeds", "signature": "def list_feeds()"}, {"kind": "method", "line": 321, "name": "get_feed", "signature": "def get_feed(name)"}, {"doc": "Fetch every registered feed (optionally clipped to a bbox).\n\nUsed by /api/feeds to populate the map in one round-trip.\n\nbbox = (min_lon, min_lat, max_lon, max_lat). Points outside the\nbbox are dropped before the response is returned.", "kind": "method", "line": 325, "name": "fetch_all", "signature": "def fetch_all(bbox, use_cache)"}, {"kind": "method", "line": 69, "name": "to_dict", "signature": "def to_dict(self)"}, {"kind": "method", "line": 82, "name": "__init__", "signature": "def __init__(self)"}, {"doc": "Subclass implementation: hit the upstream and return points.", "kind": "method", "line": 87, "name": "_fetch", "signature": "def _fetch(self)"}, {"doc": "Public entrypoint. Reads/writes the on-disk cache.\n\nThe cache is a simple `{fetched_at, points}` JSON blob —\ngood enough for a self-hosted deployment. A real production\nsystem would want a TTL'd Redis layer, but that pulls in a\nnetwork dependency.", "kind": "method", "line": 90, "name": "fetch", "signature": "def fetch(self)"}, {"doc": "Default: return (lat, lon) if both present.", "kind": "method", "line": 135, "name": "point", "signature": "def point(self, record)"}, {"kind": "method", "line": 156, "name": "_fetch", "signature": "def _fetch(self)"}, {"kind": "method", "line": 209, "name": "_fetch", "signature": "def _fetch(self)"}, {"kind": "method", "line": 271, "name": "_fetch", "signature": "def _fetch(self)"}]}, {"doc": "estorides_core.fusion_analytics =============================== Intelligence analytics layer over the fusion store.  Provides the \"dashboard\" queries that Palantir's Athena delivers: multi-source consensus, entity timelines, source reliability stats, and corroboration analytics. All read-only, all parameterized SQL, all fail-soft.  Public surface: analytics = FusionAnalytics(fusion_store_instance) analytics.entity_timeline(eid) -> dict | None analytics.entity_summary(eid) -> dict | None analytics.source_stats(source_name) -> dict | None analytics.multi_source_consensus(eid, key) -> dict analytics.corroborated_properties(eid, min_sources=2) -> list analytics.entity_search(term, ...) -> list analytics.top_changed(days=7, limit=20) -> list analytics.source_corroboration_matrix(limit=20) -> list", "id": "estorides_core/fusion_analytics.py", "kind": "module", "label": "fusion_analytics.py", "language": "py", "sha256": "f0617a9edfc7112e", "symbol_count": 14, "symbols": [{"doc": "Read-only analytics queries over the fusion store.\n\nEvery method is parameterized SQL — no string interpolation.\nA ``store`` of ``None`` is silently handled (all methods return\nempty results) so the caller never needs to guard against a\nmissing datastore.", "kind": "class", "line": 31, "name": "FusionAnalytics", "signature": "class FusionAnalytics"}, {"kind": "method", "line": 40, "name": "__init__", "signature": "def __init__(self, store)"}, {"kind": "method", "line": 46, "name": "entity_timeline", "signature": "def entity_timeline(self, eid)"}, {"kind": "method", "line": 132, "name": "entity_summary", "signature": "def entity_summary(self, eid)"}, {"kind": "method", "line": 213, "name": "source_stats", "signature": "def source_stats(self, source_name)"}, {"kind": "method", "line": 290, "name": "multi_source_consensus", "signature": "def multi_source_consensus(self, eid, key)"}, {"kind": "method", "line": 343, "name": "corroborated_properties", "signature": "def corroborated_properties(self, eid, min_sources)"}, {"kind": "method", "line": 374, "name": "entity_search", "signature": "def entity_search(self, term, etype)"}, {"kind": "method", "line": 432, "name": "top_changed", "signature": "def top_changed(self, days, limit)"}, {"kind": "method", "line": 477, "name": "source_corroboration_matrix", "signature": "def source_corroboration_matrix(self, limit)"}, {"kind": "method", "line": 509, "name": "_resolve_entity_value", "signature": "def _resolve_entity_value(self, eid)"}, {"kind": "method", "line": 520, "name": "_resolve_entity_type", "signature": "def _resolve_entity_type(self, eid)"}, {"kind": "method", "line": 532, "name": "_intel_level", "signature": "def _intel_level(source_count, sources)"}, {"kind": "method", "line": 540, "name": "_deduplicate_relationships", "signature": "def _deduplicate_relationships(rels)"}]}, {"doc": "estorides_core.fusion_store =========================== The data-fusion datastore — the single place where everything every source (loaded from the YAML catalogue) ever produced is fused, cross-run, into one queryable intelligence picture.  Why this exists --------------- The case store (:mod:`estorides_core.cases`) keeps a *per-run silo*: each investigation writes its own copy of the observations and entities it saw, keyed by ``case_id``. The same real-world entity seen across fifty runs becomes fifty rows, and nothing in the relational store answers \"show me everything we know about X, from every source, across every case\".  The fusion store closes that gap. It is the relational analogue of the Kùzu graph: a normalised, deduplicated, source-attributed fact base that accumulates across runs. The fusion is provenance-preserving — when two sources corroborate the same entity or property, the record is merged but *every* contributing source is retained, so confidence is grounded in how many independent feeds agree.  Schema (five tables) -------------------- fusion_sources         the YAML source catalogue, with fetch counters fusion_entities        canonical, cross-run, deduplicated entities fusion_entity_sources  which sources corroborated each entity (provenance) fusion_observations    every source response, fused across runs fusion_properties      attribute key/value per entity, attributed to source fusion_relationships   edges between entities, attributed to source", "id": "estorides_core/fusion_store.py", "kind": "module", "label": "fusion_store.py", "language": "py", "sha256": "a3266bfb69df9f1d", "symbol_count": 18, "symbols": [{"doc": "Deterministic, run-independent id for an entity.\n\nDerived from the type and the normalised value so the same real-world\nentity always hashes to the same id no matter which run or source first\nproduced it — the property that makes cross-run fusion automatic.", "kind": "function", "line": 177, "name": "entity_id", "signature": "def entity_id(etype, value, normalized)"}, {"doc": "Thread-safe SQLite-backed fusion datastore.\n\nOne serialised connection guarded by a lock, WAL journalling, and\n``ON DELETE CASCADE`` foreign keys so dropping an entity reaps its\nprovenance, properties and edges.", "kind": "class", "line": 190, "name": "FusionStore", "signature": "class FusionStore(SqliteStore)"}, {"doc": "Open the fusion store, returning None instead of raising on failure.\n\nThe orchestrator calls this so an unwritable or locked data directory\ndegrades the fusion layer to a no-op rather than aborting an\ninvestigation.", "kind": "method", "line": 723, "name": "open_store", "signature": "def open_store(path)"}, {"doc": "Insert a minimal entity row if absent and return its id.\n\nUsed for relationship endpoints (a country, an ASN, a port) that no\nentity-extraction pass produced, so an edge is always navigable from\nboth ends. Runs inside the caller's transaction; never overwrites an\nalready-fused entity (``INSERT OR IGNORE``).", "kind": "method", "line": 202, "name": "_ensure_entity_stub", "signature": "def _ensure_entity_stub(conn, etype, value)"}, {"doc": "Mirror the YAML source catalogue into the store.\n\nIdempotent: an existing source keeps its ``first_seen`` and its\naccumulated counters; only the descriptive columns are refreshed so\nthe catalogue tracks edits to the YAML without losing fetch history.", "kind": "method", "line": 222, "name": "register_sources", "signature": "def register_sources(self, sources)"}, {"doc": "Fuse a single source response into the cross-run observation log\nand bump the source's fetch/ok counters.", "kind": "method", "line": 259, "name": "add_observation", "signature": "def add_observation(self, observation)"}, {"doc": "Fuse one entity into the canonical store and return its id.\n\nThe ``(type, normalized)`` pair is the dedup key. On a repeat sighting\nthe row's ``last_seen``, confidence (Bayesian merge), and observation\ncount advance, and every contributing source is recorded in\n``fusion_entity_sources`` so provenance survives the merge.\n\nConfidence uses the :mod:`reliability_scoring` pipeline: source\nreliability, source type hierarchy, and corroboration count are\nfactored into the score instead of a raw ``MAX()``. A tertiary or\nunreliable source cannot override a well-corroborated primary one.", "kind": "method", "line": 300, "name": "fuse_entity", "signature": "def fuse_entity(self, entity)"}, {"doc": "Fuse a batch of entities, returning the list of fused ids.", "kind": "method", "line": 403, "name": "fuse_entities", "signature": "def fuse_entities(self, entities)"}, {"doc": "Fuse the flat scalar attributes of a parsed observation onto an\nentity, attributed to ``source``.\n\nOnly one nesting level of scalar values is taken: deep structures are\nthe observation payload's job, not an entity attribute's. Returns the\nnumber of properties written. The ``(entity_id, key, value, source)``\nprimary key means re-running the same source is idempotent while a\n*different* source asserting the same key/value adds corroboration.", "kind": "method", "line": 416, "name": "fuse_properties", "signature": "def fuse_properties(self, eid, parsed, source)"}, {"doc": "Fuse one directed edge between two entities, attributed to source.\n\nBoth endpoints are resolved to their deterministic fusion ids so the\nedge joins the same canonical entities the entity table holds.\nConfidence uses the :mod:`reliability_scoring` pipeline instead of a\nraw ``MAX()``, so a low-reliability source cannot inflate the score\nof a well-corroborated edge.", "kind": "method", "line": 462, "name": "fuse_relationship", "signature": "def fuse_relationship(self, src_type, src_value, relation, dst_type, dst_value)"}, {"doc": "Mirror the analytic edges of a knowledge graph into the store.\n\nReads node ``type``/``value`` off each endpoint and skips the pure\nplumbing relations (``observed_by``, ``co_occurs``, ``mentions``) and\nany edge touching a ``source`` node, so only pivot-worthy facts land.\nReturns the number of edges fused. Best-effort: a malformed graph is\nswallowed rather than aborting a run.", "kind": "method", "line": 532, "name": "fuse_graph", "signature": "def fuse_graph(self, kg)"}, {"doc": "Return one fused entity with its provenance, properties and edges.", "kind": "method", "line": 568, "name": "get_entity", "signature": "def get_entity(self, eid)"}, {"doc": "Search fused entities by value substring and/or type.\n\n``min_sources`` filters to entities corroborated by at least N feeds —\nthe fusion-native \"only show me what more than one source agrees on\"\nquery. Ordered by source breadth then recency.", "kind": "method", "line": 614, "name": "search_entities", "signature": "def search_entities(self, term, etype)"}, {"doc": "Return an entity's properties that at least ``min_sources`` distinct\nfeeds independently asserted — the fusion store's confidence signal.", "kind": "method", "line": 657, "name": "corroborated_properties", "signature": "def corroborated_properties(self, eid, min_sources)"}, {"doc": "Return the source catalogue with accumulated fetch/ok counters.", "kind": "method", "line": 674, "name": "list_sources", "signature": "def list_sources(self, limit)"}, {"doc": "One-glance dashboard of the fused store's size.", "kind": "method", "line": 692, "name": "stats", "signature": "def stats(self)"}, {"kind": "method", "line": 73, "name": "normalize_value", "signature": "def normalize_value(etype, value)"}, {"kind": "method", "line": 695, "name": "_count", "signature": "def _count(table)"}]}, {"doc": "graph_bundle: payload circle 2D + sphere 3D estilo ReadMenator.  Porta ``readmenator/_bundlegraph.py`` (``BundleGraphRenderer.build_payload``) y ``readmenator/_graphlayout.py`` (``hierarchical_edge_bundling`` + ``spherical_edge_bundling`` + Fibonacci + B-spline, Holten 2006) al grafo OSINT de Estorides.  Puro y sin I/O: consume el payload RAW de ``estorides_core.graph_force.build_force_payload`` y devuelve el payload bundle ``{nodes, groups, edges}`` con la misma forma que ReadMenator (para que el JS porteado sea casi verbatim). Todo input hostil se valida fail-closed (``TypeError``) o se trunca a limites fijos. Nunca genera HTML.", "id": "estorides_core/graph_bundle.py", "kind": "module", "label": "graph_bundle.py", "language": "py", "sha256": "76b6266715581086", "symbol_count": 18, "symbols": [{"doc": "Resultado del bundling circular (igual que ReadMenator).", "kind": "class", "line": 37, "name": "BundleLayout", "signature": "class BundleLayout"}, {"doc": "Resultado del bundling esferico (igual que ReadMenator).", "kind": "class", "line": 49, "name": "SphereBundleLayout", "signature": "class SphereBundleLayout"}, {"kind": "method", "line": 59, "name": "_req_str", "signature": "def _req_str(item, key, default, what)"}, {"kind": "method", "line": 68, "name": "_opt_str", "signature": "def _opt_str(item, key, default)"}, {"kind": "method", "line": 73, "name": "_opt_float", "signature": "def _opt_float(item, key, default)"}, {"kind": "method", "line": 84, "name": "_opt_int", "signature": "def _opt_int(item, key, default)"}, {"kind": "method", "line": 94, "name": "_opt_community", "signature": "def _opt_community(value)"}, {"doc": "Muestrea una B-spline cubica uniforme clamped (cualquier dimension).", "kind": "method", "line": 102, "name": "_bspline", "signature": "def _bspline(control, samples)"}, {"doc": "Rutea cada arista por la jerarquia y la muestrea como B-spline.", "kind": "method", "line": 135, "name": "_bundle_curves", "signature": "def _bundle_curves(leaves, member_group, hub, root, edges, beta, samples)"}, {"doc": "Hojas en un circulo por grupo; aristas por la jerarquia (Holten 2006).", "kind": "method", "line": 171, "name": "hierarchical_edge_bundling", "signature": "def hierarchical_edge_bundling(groups, edges, center, radius, beta, samples, group_gap, inner_ratio)"}, {"doc": "Vectores unitarios casi uniformes en espiral de angulo dorado.", "kind": "method", "line": 215, "name": "fibonacci_sphere", "signature": "def fibonacci_sphere(count)"}, {"kind": "method", "line": 231, "name": "_unit", "signature": "def _unit(v)"}, {"doc": "Biseca el lattice entre runs de grupos hasta un cap contiguo por grupo.", "kind": "method", "line": 238, "name": "_split_caps", "signature": "def _split_caps(labels, sizes, points, lattice, out)"}, {"doc": "Hojas en caps esfericos por comunidad; aristas por la jerarquia.", "kind": "method", "line": 267, "name": "spherical_edge_bundling", "signature": "def spherical_edge_bundling(groups, edges, radius, beta, samples, inner_ratio)"}, {"kind": "method", "line": 316, "name": "_round2", "signature": "def _round2(pt)"}, {"kind": "method", "line": 320, "name": "_round3", "signature": "def _round3(pt)"}, {"doc": "Defaults de la pagina bundle (fuente unica para el JS).\n\nMismos valores que ``readmenator/_config.py`` BUNDLEGRAPH_*; cada uno\nse puede afinar por entorno sin romper el import (env malformada →\ndefault, nunca raise). Los colores viajan por nodo/grupo (campos\n``c``), asi que no hay mapa separado.", "kind": "method", "line": 324, "name": "bundle_settings", "signature": "def bundle_settings()"}, {"doc": "Deriva el payload bundle desde un payload force RAW.\n\nLas comunidades conservan el orden del analizador (id asc) y los nodos\nsin comunidad forman el grupo trailing ``unassigned``. Dentro de cada\ngrupo los nodos van por ``rank`` desc (hubs al centro del arco/cap).\nSolo viajan aristas entidad→entidad de relacion (el scaffolding\n``member_of``/``layered_as`` se queda fuera: la jerarquia ya lo pinta).", "kind": "method", "line": 359, "name": "build_bundle_payload", "signature": "def build_bundle_payload(force_payload)"}]}, {"doc": "graph_force3d: payload force-graph estilo ReadMenator + contexto IA.  Porta ``readmenator/_forcegraph.py`` (payload heterogeneo + SETTINGS) y el formato RAW de ``graph-force.html`` al grafo OSINT de Estorides, mas un constructor de contexto markdown con presupuesto de tokens para la capa de IA local (GraphRAG extractivo, sin llamadas a LLM).  Puro y sin I/O: todo input hostil (labels remotos) se valida fail-closed (``TypeError`` ante valores no JSON-safe) o se trunca a limites fijos. El modulo nunca genera HTML: solo dicts JSON-safe.", "id": "estorides_core/graph_force.py", "kind": "module", "label": "graph_force.py", "language": "py", "sha256": "7ef644aa9f5c44ce", "symbol_count": 12, "symbols": [{"doc": "Deriva un color HSL estable desde un label (djb2, como ReadMenator).", "kind": "function", "line": 48, "name": "family_color_from_name", "signature": "def family_color_from_name(name, sat_base, sat_span, light_base, light_span)"}, {"doc": "Escala log2 del tamano de nodo (minimo 1).", "kind": "function", "line": 65, "name": "node_value", "signature": "def node_value(symbols, degree, findings)"}, {"doc": "Valores SETTINGS de ReadMenator graph-force.html (fuente unica).", "kind": "function", "line": 70, "name": "force_settings", "signature": "def force_settings()"}, {"doc": "Lee un campo string obligatorio; fail-closed ante no-str.", "kind": "function", "line": 97, "name": "_req_str", "signature": "def _req_str(item, key, default, what)"}, {"doc": "Lee un campo de estilo; ante no-str usa el default (no mata el grafo).", "kind": "function", "line": 107, "name": "_opt_str", "signature": "def _opt_str(item, key, default)"}, {"doc": "Lee un campo entero; ante basura usa el default.", "kind": "function", "line": 113, "name": "_opt_int", "signature": "def _opt_int(item, key, default)"}, {"doc": "Grado por nodo + edges validos (ambos extremos conocidos).", "kind": "function", "line": 124, "name": "_degrees", "signature": "def _degrees(node_ids, edges)"}, {"doc": "Un edge es puente si lo declara o si une clusters distintos.", "kind": "function", "line": 150, "name": "_is_bridge", "signature": "def _is_bridge(edge)"}, {"doc": "Convierte nodos/edges OSINT (`/api/graph`) al formato RAW force-graph.\n\nTruncacion determinista: grado desc, puentes inter-cluster primero,\ndesempate por `id`. Los edges huerfanos se descartan (conteo en meta).", "kind": "function", "line": 161, "name": "build_force_payload", "signature": "def build_force_payload(nodes, edges, clusters, max_nodes, max_edges)"}, {"doc": "Neutraliza un string remoto para embeberlo en markdown extractivo.", "kind": "function", "line": 332, "name": "_md_safe", "signature": "def _md_safe(text, limit)"}, {"doc": "Corte duro por linea completa + marcador (fences nunca se emiten).", "kind": "function", "line": 341, "name": "_truncate_lines", "signature": "def _truncate_lines(markdown, budget)"}, {"doc": "Contexto markdown extractivo con presupuesto para la IA local.\n\nSin llamadas a LLM: entidades top por grado, puentes inter-cluster\nprimero, una linea por comunidad. Determinista y acotado.", "kind": "function", "line": 353, "name": "build_ai_context", "signature": "def build_ai_context(nodes, edges, clusters, budget_chars)"}]}, {"doc": "estorides_core.graph_kuzu ========================= Persistent graph backend powered by Kuzu (embedded Cypher DB).  Why Kuzu and not just another NetworkX dump: NetworkX on disk is read whole on every request. Kuzu gives us indexed Cypher queries so we can ask \"every entity connected to <id> within 2 hops\" without reloading the world. We still keep NetworkX as the in-memory working graph (the orchestrator depends on it for per-run traversal) — this module mirrors every write to a Kuzu database on disk so cross-run intelligence queries become fast.  Schema mirrors the in-memory graph's node kinds:  Entity nodes: Domain, IPv4, IPv6, Email, URL, Person, Org, ASN, CVE, GHSA, BTCAddress, ETHAddress, Phone, Hash (md5/sha1/sha256/bitcoin_tx), MAC, UserAgent, Country, Sanction Source nodes: one node per OSINT source  Edges: OBSERVED_BY   (Entity -> Source)   \"this source produced this entity\" CO_OCCURS     (Entity -> Entity)   \"appeared in the same response\" RESOLVED_TO   (Domain -> IPv4)     \"DNS resolution\" REGISTERED_BY (Domain -> Org)      \"WHOIS registrant\" HAS_CVE       (IP -> CVE)          \"vulnerability on this host\" LOCATED_IN    (Entity -> Country)  \"geolocated entity\" SANCTIONED    (Entity -> Sanction) \"OFAC SDN match\" SAME_AS       (Entity -> Entity)   \"fuzzy cluster\"", "id": "estorides_core/graph_kuzu.py", "kind": "module", "label": "graph_kuzu.py", "language": "py", "sha256": "461191d692ffc142", "symbol_count": 11, "symbols": [{"kind": "function", "line": 124, "name": "_label_for", "signature": "def _label_for(ent_type)"}, {"doc": "Canonical id used as PRIMARY KEY in Kuzu.\n\nMirrors the in-memory convention in KnowledgeGraph._node_id so a\n`Domain:evilcorp.com` row means the same thing in both stores.", "kind": "function", "line": 133, "name": "_node_id", "signature": "def _node_id(type_, value)"}, {"doc": "Thread-safe Kuzu wrapper for the Estorides knowledge graph.\n\nThe orchestrator calls this alongside the in-memory NetworkX graph.\nWrites are synchronous but cheap; reads (Cypher) are also synchronous\nand roughly match NetworkX traversal cost for 2-hop queries while\nbeing dramatically faster at cross-run joins.", "kind": "class", "line": 196, "name": "KuzuGraphBackend", "signature": "class KuzuGraphBackend"}, {"kind": "method", "line": 205, "name": "__init__", "signature": "def __init__(self, path)"}, {"kind": "method", "line": 233, "name": "_init_schema", "signature": "def _init_schema(self)"}, {"doc": "Insert (or merge) an entity. Returns its canonical node id.\n\nIf `source` is given we also wire an OBSERVED_BY edge so we\ncan later ask \"which sources saw this entity?\".", "kind": "method", "line": 245, "name": "upsert_entity", "signature": "def upsert_entity(self, ent_type, value, source)"}, {"doc": "Insert an edge between two entities.\n\nUnknown relations are silently skipped — every edge type\nthat matters is mapped in _RELATION_TO_EDGE.", "kind": "method", "line": 295, "name": "upsert_relationship", "signature": "def upsert_relationship(self, src_type, src_value, rel, dst_type, dst_value)"}, {"doc": "Return nodes reachable from `node_id` within `hops` edges.\n\n`relation` optionally filters to a single edge label.\nReturns a list of dicts with whatever columns the query names.", "kind": "method", "line": 346, "name": "neighbors", "signature": "def neighbors(self, node_id, hops, relation, limit)"}, {"doc": "Run a Cypher query and return rows as a list of dicts.\n\nColumn names are taken from the RETURN clause. Missing\ncolumns (e.g. `m.kind` on a node that was never enriched) come\nback as None so callers don't have to special-case.", "kind": "method", "line": 379, "name": "cypher", "signature": "def cypher(self, query, params)"}, {"doc": "Return counts of every node label and edge rel type.", "kind": "method", "line": 406, "name": "stats", "signature": "def stats(self)"}, {"kind": "method", "line": 436, "name": "close", "signature": "def close(self)"}]}, {"doc": "graph_rag_search: GraphRAG local/global para la IA local (sin LLM).  Porta ``readmenator/_graphrag.py`` + ``_rank.py`` al grafo OSINT: indice tipado (entidades, relaciones, text units, reportes de comunidad), BM25 lexico, PageRank global, Personalized PageRank (busqueda local) y map-reduce sobre reportes (busqueda global). Todo extractivo, determinista, puro y sin I/O.", "id": "estorides_core/graph_rag_search.py", "kind": "module", "label": "graph_rag_search.py", "language": "py", "sha256": "306ad90dd6eb250e", "symbol_count": 44, "symbols": [{"doc": "Presupuestos y pesos de retrieval (env `ESTORIDES_GRAPHRAG_*`).", "kind": "class", "line": 53, "name": "GraphRagConfig", "signature": "class GraphRagConfig"}, {"doc": "Construye la config desde env; malformada → defaults (nunca raise).", "kind": "method", "line": 75, "name": "graph_rag_config_from_env", "signature": "def graph_rag_config_from_env()"}, {"doc": "Parte en tokens minusculos; camelCase se divide y se conserva entero.", "kind": "method", "line": 98, "name": "tokenize", "signature": "def tokenize(text, min_len, stopwords)"}, {"doc": "Okapi BM25 sobre un corpus fijo de listas de tokens.", "kind": "class", "line": 112, "name": "Bm25Index", "signature": "class Bm25Index"}, {"doc": "Filas estocasticas (indice, prob) + nodos dangling, orden determinista.", "kind": "method", "line": 153, "name": "_stochastic", "signature": "def _stochastic(ids, edges)"}, {"doc": "PageRank dirigido y pesado; los scores suman 1 (determinista).", "kind": "method", "line": 176, "name": "pagerank", "signature": "def pagerank(ids, edges, alpha, max_iter, tolerance)"}, {"doc": "PPR con teletransporte al seed; seeds ajenos se ignoran.", "kind": "method", "line": 207, "name": "personalized_pagerank", "signature": "def personalized_pagerank(ids, edges, seeds, alpha, max_iter, tolerance)"}, {"doc": "Gemelo de graph_force._md_safe: neutraliza markdown hostil.", "kind": "method", "line": 250, "name": "_md_safe", "signature": "def _md_safe(text, limit)"}, {"kind": "class", "line": 260, "name": "RagEntity", "signature": "class RagEntity"}, {"kind": "class", "line": 271, "name": "RagRelation", "signature": "class RagRelation"}, {"kind": "class", "line": 281, "name": "RagTextUnit", "signature": "class RagTextUnit"}, {"kind": "class", "line": 289, "name": "RagCommunity", "signature": "class RagCommunity"}, {"kind": "class", "line": 305, "name": "RagContext", "signature": "class RagContext"}, {"kind": "class", "line": 317, "name": "GraphRagIndex", "signature": "class GraphRagIndex"}, {"kind": "method", "line": 354, "name": "_req_str", "signature": "def _req_str(item, key, default, what)"}, {"kind": "method", "line": 363, "name": "_opt_str", "signature": "def _opt_str(item, key, default)"}, {"kind": "method", "line": 368, "name": "_opt_int", "signature": "def _opt_int(item, key, default)"}, {"kind": "method", "line": 378, "name": "_rel_weight", "signature": "def _rel_weight(relation)"}, {"doc": "Indice extractivo desde nodos/edges estilo `/api/graph` (puro).", "kind": "method", "line": 382, "name": "build_index", "signature": "def build_index(nodes, edges, clusters, config)"}, {"doc": "Un reporte extractivo por cluster + raiz (determinista).", "kind": "method", "line": 491, "name": "_reports", "signature": "def _reports(nodes, degree, rank, top_mass, weighted, cluster_label, cfg)"}, {"doc": "BM25 + PPR (local) y map-reduce sobre reportes (global).", "kind": "class", "line": 593, "name": "GraphRagSearcher", "signature": "class GraphRagSearcher"}, {"doc": "One-shot: build + search (el camino que usa la capa IA local).", "kind": "method", "line": 857, "name": "ask_context", "signature": "def ask_context(query, nodes, edges, clusters, budget_tokens, mode, config)"}, {"doc": "Detalle extractivo desde atributos escalares (no escalares se ignoran).", "kind": "method", "line": 872, "name": "_scalar_detail", "signature": "def _scalar_detail(attrs)"}, {"doc": "Bloque markdown para el prompt LLM desde un grafo networkx (duck-typing).\n\nGrafo vacío → `\"\"` (el caller deja el prompt intacto).", "kind": "method", "line": 886, "name": "graph_context_block", "signature": "def graph_context_block(query, nx_graph, cluster_of, budget_tokens, mode, config)"}, {"kind": "method", "line": 115, "name": "__init__", "signature": "def __init__(self, documents, k1, b)"}, {"doc": "Un score BM25 por documento, en orden de corpus.", "kind": "method", "line": 136, "name": "scores", "signature": "def scores(self, query)"}, {"kind": "method", "line": 324, "name": "to_dict", "signature": "def to_dict(self)"}, {"kind": "method", "line": 334, "name": "from_dict", "signature": "def from_dict(cls, data)"}, {"kind": "method", "line": 446, "name": "where", "signature": "def where(cid)"}, {"kind": "method", "line": 596, "name": "__init__", "signature": "def __init__(self, config, index)"}, {"kind": "method", "line": 621, "name": "_tok", "signature": "def _tok(self, text)"}, {"kind": "method", "line": 624, "name": "_entity_doc", "signature": "def _entity_doc(self, entity)"}, {"kind": "method", "line": 628, "name": "_symmetric_edges", "signature": "def _symmetric_edges(self)"}, {"kind": "method", "line": 635, "name": "choose_mode", "signature": "def choose_mode(self, query)"}, {"kind": "method", "line": 644, "name": "search", "signature": "def search(self, query, mode, budget_tokens)"}, {"kind": "method", "line": 652, "name": "_budget_chars", "signature": "def _budget_chars(self, budget_tokens)"}, {"kind": "method", "line": 658, "name": "local_search", "signature": "def local_search(self, query, budget_tokens)"}, {"kind": "method", "line": 754, "name": "global_search", "signature": "def global_search(self, query, budget_tokens)"}, {"kind": "method", "line": 794, "name": "_report_block", "signature": "def _report_block(report)"}, {"kind": "method", "line": 801, "name": "_unit_block", "signature": "def _unit_block(unit)"}, {"kind": "method", "line": 804, "name": "_pack", "signature": "def _pack(self, header, sections, budget_tokens, shares)"}, {"doc": "Empaqueta por shares; devuelve (texto, hubo_recorte).", "kind": "method", "line": 823, "name": "_pack_with", "signature": "def _pack_with(self, header, sections, shares, budget)"}, {"kind": "method", "line": 335, "name": "rows", "signature": "def rows(name)"}, {"kind": "method", "line": 341, "name": "meta", "signature": "def meta()"}]}, {"doc": "estorides_core.hypothesis_engine ================================ The \"data → information\" layer.  Consumes the per-run ``observations`` and ``entities`` produced by the orchestrator and emits a deduplicated, scored, auditable list of typed ``Hypothesis`` records. Each hypothesis carries the evidence items that justify it (source, field, value, reliability-weighted weight) and a human-readable ``claim`` and ``reasoning`` derived from templates, not from the LLM.  Pure module: no I/O, no DB writes, no logging, no clock. Same input ⇒ same output, bit-by-bit. Hypothesis ids are content hashes so duplicate runs produce the same ids and the fusion store can deduplicate across runs.  Public surface::  generate_hypotheses(observations, entities, kg=None, min_score=0.10, max_hypotheses=50) -> list[Hypothesis]", "id": "estorides_core/hypothesis_engine.py", "kind": "module", "label": "hypothesis_engine.py", "language": "py", "sha256": "bc33204cb0e8593e", "symbol_count": 23, "symbols": [{"doc": "A typed reference to an entity involved in a hypothesis.", "kind": "class", "line": 57, "name": "EntityRef", "signature": "class EntityRef"}, {"doc": "One piece of supporting or contradicting evidence.", "kind": "class", "line": 65, "name": "Evidence", "signature": "class Evidence"}, {"doc": "A typed, scored, auditable intelligence conclusion.", "kind": "class", "line": 76, "name": "Hypothesis", "signature": "class Hypothesis"}, {"doc": "Stringify a value, bounded to ``_VALUE_MAX_CHARS``.", "kind": "method", "line": 113, "name": "_truncate", "signature": "def _truncate(value)"}, {"kind": "method", "line": 123, "name": "_is_mapping", "signature": "def _is_mapping(value)"}, {"doc": "Build ``{type: {value, value, ...}}`` from the entity list.\n\nTolerates both ``Entity`` dataclass instances and plain dicts\n(the orchestrator returns both shapes depending on the call site).", "kind": "method", "line": 127, "name": "_entity_lookup", "signature": "def _entity_lookup(entities)"}, {"doc": "Deterministic 16-char hex id for a hypothesis.", "kind": "method", "line": 148, "name": "_hypothesis_id", "signature": "def _hypothesis_id(htype, entity_refs, supporting)"}, {"doc": "Net-support score in (0, 1].\n\n``supporting / (supporting + contradicting + floor)`` — the floor\nprevents division by zero and prevents a single weak item from\nproducing a \"1.0 certain\" score.", "kind": "method", "line": 166, "name": "_score", "signature": "def _score(supporting, contradicting)"}, {"doc": "Reliability-weighted confidence via :mod:`reliability_scoring`.", "kind": "method", "line": 180, "name": "_confidence", "signature": "def _confidence(supporting, contradicting)"}, {"kind": "method", "line": 201, "name": "_clip_claim", "signature": "def _clip_claim(template)"}, {"doc": "Strategy: turn a (observations, entities) snapshot into hypotheses.", "kind": "class", "line": 210, "name": "HypothesisGenerator", "signature": "class HypothesisGenerator(Protocol)"}, {"doc": "`domain-belongsto-actor`: a domain's WHOIS/issuer/hosting org matches an entity.", "kind": "method", "line": 220, "name": "_domain_belongsto_actor", "signature": "def _domain_belongsto_actor(observations, entities)"}, {"doc": "Best-effort: extract domain-like values from a single observation.\n\nThe orchestrator doesn't always stamp the queried domain on the\nobservation, so this is a soft hint. Returns ``[]`` if nothing\nlooks like a domain.", "kind": "method", "line": 312, "name": "_domains_in_obs", "signature": "def _domains_in_obs(obs)"}, {"doc": "`email-aliasto-person`: an email and a person name appear together in one obs.", "kind": "method", "line": 333, "name": "_email_aliases_person", "signature": "def _email_aliases_person(observations, entities)"}, {"doc": "Find a value that looks like an email anywhere in the parsed dict.", "kind": "method", "line": 393, "name": "_extract_email", "signature": "def _extract_email(parsed)"}, {"doc": "Find a value that looks like a person name (has a space, no @, no path).", "kind": "method", "line": 404, "name": "_extract_person_name", "signature": "def _extract_person_name(parsed)"}, {"doc": "`ip-shared-infra`: >=2 domains resolve to the same IP.", "kind": "method", "line": 415, "name": "_ip_shared_infra", "signature": "def _ip_shared_infra(observations, entities)"}, {"doc": "Best-effort: pull IPv4-looking values out of a parsed payload.", "kind": "method", "line": 496, "name": "_extract_ips", "signature": "def _extract_ips(parsed)"}, {"kind": "method", "line": 511, "name": "_looks_like_ipv4", "signature": "def _looks_like_ipv4(s)"}, {"doc": "`asn-shared-infra`: >=3 entities of the run live in the same ASN.", "kind": "method", "line": 524, "name": "_asn_shared_infra", "signature": "def _asn_shared_infra(observations, entities)"}, {"doc": "Best-effort: pull an AS-number-ish value out of the parsed payload.", "kind": "method", "line": 588, "name": "_extract_asn", "signature": "def _extract_asn(parsed)"}, {"doc": "Generate typed, scored, auditable hypotheses for a run.\n\nPure: no I/O, no DB writes, no logging, no clock. Same input ⇒\nsame output bit-by-bit. Hypothesis ids are content hashes so the\nfusion store can deduplicate across runs.\n\nParameters\n----------\nobservations\n    Per-source observations from the orchestrator. Each one is\n    expected to have at least ``source`` and ``parsed``; the\n    generator skips malformed entries without raising.\nentities\n    The deduplicated entity list from the orchestrator. Each\n    item can be a dict (``{\"type\": ..., \"value\": ...}``) or an\n    ``Entity`` dataclass.\nkg\n    Optional knowledge graph. Reserved for future generators\n    (ego-network, motif-based) — ignored in v1.\nmin_score\n    Floor for the output: hypotheses with score below this are\n    dropped. ``[0, 1]``.\nmax_hypotheses\n    Hard cap on the returned list. Top by score.", "kind": "method", "line": 613, "name": "generate_hypotheses", "signature": "def generate_hypotheses(observations, entities, kg)"}, {"kind": "method", "line": 213, "name": "__call__", "signature": "def __call__(self, observations, entities)"}]}, {"doc": "estorides_core.ids ================== Deterministic content-addressed identifiers.  Six modules (fusion_store, entity_resolution, recon_fusion, change_detection, hypothesis_engine, target_management) each hand-rolled the same `sha1(payload.encode(\"utf-8\"), usedforsecurity=False).hexdigest()[:16]` idiom. SHA-1 here is a content hash for deduplication, **not** a security primitive, hence `usedforsecurity=False` (also silences FIPS-mode concerns).  `stable_id` is the single implementation. Callers own the payload shape (`f\"{type}:{normalized}\"`, `\"|\".join([...])`, …) so existing ids are byte-for-byte preserved.", "id": "estorides_core/ids.py", "kind": "module", "label": "ids.py", "language": "py", "sha256": "f1cdfc7c3e5a5af2", "symbol_count": 1, "symbols": [{"doc": "Deterministic hex id of `length` chars for a UTF-8 payload.", "kind": "function", "line": 21, "name": "stable_id", "signature": "def stable_id(payload, length)"}]}, {"doc": "estorides_core.intel_resolver ============================= Cross-feed entity resolver. Inspired by Osiris' `intel/server.js` (simplifaisoul/osiris), the resolver is the single entry point that takes a `(type, id)` pair and returns nodes + edges connecting it across every feed we have a primitive for:  type=ip         -> ASN, ISP, organisation, country (Wikidata + IP-API) type=domain     -> registrant org, ASN, country, sanctions cross-check type=company    -> parent org, country, sanctions (Wikidata + OFAC SDN) type=person     -> employer, nationality, sanctions (Wikidata + OFAC SDN) type=country    -> region, neighbours, risk score type=cve        -> affected vendors, exploits (NVD/EPSS cross-check)  The resolver never *fetches* the source. It composes the local cache (`SanctionsIndex`, `WikidataCache`) with on-demand SPARQL queries when a node is missing. The orchestrator's post-process stage can call this once per significant entity and wire the result into the knowledge graph + Kuzu backend.  Public surface:  resolver = EntityResolver() out = resolver.resolve(\"ip\", \"1.2.3.4\") out = resolver.resolve(\"company\", \"Acme Corp\") out = resolver.resolve(\"person\", \"Vladimir Putin\")  Return shape (matches Osiris' `nodes`/`links` for drop-in reuse):", "id": "estorides_core/intel_resolver.py", "kind": "module", "label": "intel_resolver.py", "language": "py", "sha256": "6bfe87d0a5a258b3", "symbol_count": 26, "symbols": [{"doc": "Execute a SPARQL SELECT against the Wikidata endpoint.\n\nReturns the list of result rows. SSRF-guarded via the URL.", "kind": "function", "line": 90, "name": "_run_sparql", "signature": "def _run_sparql(query)"}, {"doc": "Pull a string value out of a SPARQL JSON row.", "kind": "function", "line": 111, "name": "_val", "signature": "def _val(row, key)"}, {"kind": "class", "line": 120, "name": "_TTLCache", "signature": "class _TTLCache"}, {"doc": "Cross-feed entity resolution.\n\nComposes Wikidata SPARQL, the OFAC SDN index (via the existing\n`OntologyEngine.sanctions`), and a couple of free IP-intel\nservices into a single response shape suitable for both the UI\n(entity graph panel) and the orchestrator (knowledge graph\nenrichment).", "kind": "class", "line": 156, "name": "EntityResolver", "signature": "class EntityResolver"}, {"kind": "method", "line": 789, "name": "_norm", "signature": "def _norm(s)"}, {"kind": "method", "line": 793, "name": "_is_valid_ipv4", "signature": "def _is_valid_ipv4(s)"}, {"kind": "method", "line": 801, "name": "_escape_sparql", "signature": "def _escape_sparql(s)"}, {"kind": "method", "line": 121, "name": "__init__", "signature": "def __init__(self)"}, {"kind": "method", "line": 127, "name": "get", "signature": "def get(self, kind, key)"}, {"kind": "method", "line": 140, "name": "put", "signature": "def put(self, kind, key, value)"}, {"kind": "method", "line": 148, "name": "stats", "signature": "def stats(self)"}, {"kind": "method", "line": 165, "name": "__init__", "signature": "def __init__(self)"}, {"kind": "method", "line": 174, "name": "resolve", "signature": "def resolve(self, ent_type, ent_id)"}, {"doc": "GET a VirusTotal v3 path, returning parsed JSON or None.\n\nReads the API key from `VT_API_KEY`; if it is absent the call\nis a silent no-op so the resolver degrades cleanly without a\nkey. SSRF-guarded via the constructed URL.", "kind": "method", "line": 214, "name": "_vt_get", "signature": "def _vt_get(self, path, limit)"}, {"doc": "Expand one VirusTotal relationship endpoint into nodes/links.\n\n`attr_field` pulls the related value from `attributes` (e.g.\n`host_name` for IP resolutions) instead of the raw object id.", "kind": "method", "line": 245, "name": "_vt_add_relationship", "signature": "def _vt_add_relationship(self, path)"}, {"doc": "Stamp a node with VirusTotal detection stats (counter-intel signal).", "kind": "method", "line": 290, "name": "_vt_flag_malicious", "signature": "def _vt_flag_malicious(self, path, node, sources)"}, {"kind": "method", "line": 306, "name": "_resolve_ip", "signature": "def _resolve_ip(self, ip)"}, {"kind": "method", "line": 412, "name": "_resolve_domain", "signature": "def _resolve_domain(self, domain)"}, {"doc": "Resolve a file hash via VirusTotal relationships.\n\nSurfaces the network footprint of a sample (contacted IPs and\ndomains, dropped/bundled files) and stamps the detection count\nso a malicious sample lights up the counter-intelligence tier.", "kind": "method", "line": 473, "name": "_resolve_file", "signature": "def _resolve_file(self, file_hash)"}, {"kind": "method", "line": 510, "name": "_resolve_company", "signature": "def _resolve_company(self, name)"}, {"kind": "method", "line": 569, "name": "_resolve_person", "signature": "def _resolve_person(self, name)"}, {"kind": "method", "line": 638, "name": "_resolve_country", "signature": "def _resolve_country(self, name)"}, {"kind": "method", "line": 678, "name": "_resolve_cve", "signature": "def _resolve_cve(self, cve_id)"}, {"kind": "method", "line": 750, "name": "_resolve_btc", "signature": "def _resolve_btc(self, addr)"}, {"kind": "method", "line": 753, "name": "_resolve_eth", "signature": "def _resolve_eth(self, addr)"}, {"kind": "method", "line": 756, "name": "_resolve_crypto", "signature": "def _resolve_crypto(self, addr, kind)"}]}, {"doc": "estorides_core.job_registry ===========================  Bounded, TTL-aware registry for in-memory job objects (RUN_STREAM_JOBS, DISCOVER_JOBS, etc.). The plain `Dict[str, _Job]` pattern that the project shipped with had no eviction at all — see issues #14, #20, #50 — which let an unauthenticated caller grow server memory without bound by repeatedly starting short-lived jobs.  Eviction policy --------------- * **LRU by insertion order** — when the registry hits `max_size`, the oldest job is dropped. Python 3.7+ dicts guarantee insertion order so `next(iter(reg))` is the oldest. * **TTL** — every job has a `created_at`; a sweep on every `register()` drops jobs older than `ttl_seconds`. Cheap when the set is small, and a sweep is unnecessary when the set is empty or the entries are fresh, so the worst-case extra cost is `O(n)` per call, which is dominated by the per-call `register()` itself.  Thread safety ------------- The Flask request handlers that touch these registries run in worker threads (gunicorn forks, multi-worker wsgi). The `register()` / `get()` / `evict_expired()` / `pop()` methods hold a single `threading.Lock` so a sweep and a `get()` cannot race. The cost is the lock acquire on every call, which is negligible compared to the JSON serialisation that follows.", "id": "estorides_core/job_registry.py", "kind": "module", "label": "job_registry.py", "language": "py", "sha256": "c68e695867a90767", "symbol_count": 10, "symbols": [{"doc": "A dict-like registry with size and time bounds.\n\n`max_size` and `ttl_seconds` are read at construction and not\nmutated — the registry is \"configured once, used many times\",\nwhich matches how the web app's STREAM config is loaded at\nimport time.", "kind": "class", "line": 40, "name": "BoundedJobRegistry", "signature": "class BoundedJobRegistry"}, {"kind": "method", "line": 49, "name": "__init__", "signature": "def __init__(self)"}, {"doc": "Insert (or replace) a job, evicting expired and overflow entries.\n\nReturns the value so the caller can use it inline:\n    job = registry.register(\"abc\", _RunStreamJob(...))", "kind": "method", "line": 60, "name": "register", "signature": "def register(self, key, value)"}, {"doc": "Return the value for `key` or None. Refreshes the LRU order.", "kind": "method", "line": 80, "name": "get", "signature": "def get(self, key)"}, {"doc": "Remove and return the value for `key`, or None.", "kind": "method", "line": 95, "name": "pop", "signature": "def pop(self, key)"}, {"kind": "method", "line": 101, "name": "keys", "signature": "def keys(self)"}, {"kind": "method", "line": 105, "name": "values", "signature": "def values(self)"}, {"kind": "method", "line": 109, "name": "__len__", "signature": "def __len__(self)"}, {"doc": "Sweep and drop TTL-expired entries. Returns the number dropped.", "kind": "method", "line": 113, "name": "evict_expired", "signature": "def evict_expired(self)"}, {"kind": "method", "line": 119, "name": "_evict_expired_locked", "signature": "def _evict_expired_locked(self, now)"}]}, {"doc": "estorides_core.knowledge_graph ============================== Persistent in-memory knowledge graph backed by NetworkX.  Nodes = entities (domain, ip, email, person, organisation, cve, etc.) Edges = relationships discovered between them * \"observed_by\"   entity -> source * \"resolved_to\"   domain -> ip * \"co_occurs\"     entity <-> entity   (appeared in the same response) * \"mentions\"      source -> entity * \"located_in\"    entity -> place * \"registered_by\" domain -> org * \"has_cve\"       ip/port -> cve  GraphML is dumped to disk for analysis in Gephi / Cytoscape / Neo4j.", "id": "estorides_core/knowledge_graph.py", "kind": "module", "label": "knowledge_graph.py", "language": "py", "sha256": "2aadb2bb53fe4227", "symbol_count": 18, "symbols": [{"doc": "Read the distinct source set off a node, tolerating GraphML's\nJSON-string serialisation of the original Python set/list.", "kind": "function", "line": 73, "name": "_node_sources", "signature": "def _node_sources(node)"}, {"kind": "class", "line": 90, "name": "KnowledgeGraph", "signature": "class KnowledgeGraph"}, {"kind": "method", "line": 91, "name": "__init__", "signature": "def __init__(self, name)"}, {"doc": "Insert an entity. Returns the node id used.", "kind": "method", "line": 97, "name": "add_entity", "signature": "def add_entity(self, entity)"}, {"doc": "Add every entity + every co-occurrence edge within the same response.", "kind": "method", "line": 127, "name": "add_observation", "signature": "def add_observation(self, source, entities)"}, {"kind": "method", "line": 142, "name": "add_relationship", "signature": "def add_relationship(self, src_type, src_value, rel, dst_type, dst_value)"}, {"kind": "method", "line": 164, "name": "export_graphml", "signature": "def export_graphml(self, path)"}, {"kind": "method", "line": 183, "name": "export_json", "signature": "def export_json(self)"}, {"doc": "Emit the graph in LazyOwn intel vocabulary (JSON-serializable).", "kind": "method", "line": 207, "name": "export_lazyown", "signature": "def export_lazyown(self)"}, {"kind": "method", "line": 243, "name": "summary", "signature": "def summary(self)"}, {"kind": "method", "line": 261, "name": "top_entities", "signature": "def top_entities(self, n, by)"}, {"doc": "Partition entity nodes into communities (clusters).\n\nRuns greedy modularity on the undirected projection, restricted\nto `nodes` when given and always excluding `source` nodes (whose\nfan-out would otherwise collapse every cluster into one). Returns\na `node_id -> community index` map. Falls back to connected\ncomponents when modularity cannot be computed (e.g. no edges).", "kind": "method", "line": 281, "name": "communities", "signature": "def communities(self, nodes)"}, {"doc": "Classify a node into the intelligence pipeline tier.\n\ndata                  single corroborating source\ninformation           >= 2 distinct sources\nintelligence          cross-cluster bridge or resolved relation\ncounter_intelligence  sanction/threat/VirusTotal-malicious signal\n\nHigher tiers win. `bridge_nodes` is the set of nodes that sit on\nan inter-cluster edge (computed once by the caller).", "kind": "method", "line": 312, "name": "intel_level", "signature": "def intel_level(self, node_id, bridge_nodes)"}, {"kind": "method", "line": 357, "name": "ego_subgraph", "signature": "def ego_subgraph(self, node_id, radius)"}, {"kind": "method", "line": 368, "name": "neighbours", "signature": "def neighbours(self, node_id, relation)"}, {"kind": "method", "line": 384, "name": "_node_id", "signature": "def _node_id(self, kind, value)"}, {"kind": "method", "line": 387, "name": "_source_node", "signature": "def _source_node(self, source)"}, {"kind": "method", "line": 397, "name": "_node_color", "signature": "def _node_color(self, ent_type)"}]}, {"doc": "estorides_core.lan_bundle ========================= LAN output deliverables for automation.  Builds inventory, graph, LazyOwn, and STIX payloads from LanDevice lists and writes them atomically to an output directory.", "id": "estorides_core/lan_bundle.py", "kind": "module", "label": "lan_bundle.py", "language": "py", "sha256": "bd8d645efa6205d5", "symbol_count": 8, "symbols": [{"doc": "Sanitize a file stem. Bad input falls back to lan.", "kind": "function", "line": 29, "name": "sanitize_stem", "signature": "def sanitize_stem(stem)"}, {"doc": "Resolve the LAN output directory from env or data default.", "kind": "function", "line": 40, "name": "default_output_dir", "signature": "def default_output_dir()"}, {"doc": "Build all four payloads. Never raises on bad items.", "kind": "function", "line": 48, "name": "build_bundle", "signature": "def build_bundle(devices)"}, {"kind": "function", "line": 331, "name": "_fmt_ts", "signature": "def _fmt_ts(ts)"}, {"kind": "function", "line": 341, "name": "_stix_ref", "signature": "def _stix_ref(node_id, id_map)"}, {"doc": "Outcome of write_bundle. Failures are values, never raises.", "kind": "class", "line": 351, "name": "LanBundleResult", "signature": "class LanBundleResult"}, {"doc": "Write all four deliverables atomically. Never raises.", "kind": "method", "line": 363, "name": "write_bundle", "signature": "def write_bundle(devices)"}, {"kind": "method", "line": 359, "name": "to_dict", "signature": "def to_dict(self)"}]}, {"doc": "estorides_core.lan_inventory ============================ Passive-first LAN device inventory.  Pure parsers for ARP/neighbor tables and DHCP leases plus MAC-keyed device merging. No sockets, no subprocess, no file reads.", "id": "estorides_core/lan_inventory.py", "kind": "module", "label": "lan_inventory.py", "language": "py", "sha256": "34d45b2e69be6b0a", "symbol_count": 12, "symbols": [{"doc": "One physical host, merged from one or more sightings.", "kind": "class", "line": 51, "name": "LanDevice", "signature": "class LanDevice"}, {"doc": "Normalize MAC to lowercase colon form, or None when invalid.", "kind": "method", "line": 64, "name": "normalize_mac", "signature": "def normalize_mac(raw)"}, {"kind": "method", "line": 77, "name": "_valid_lan_ip", "signature": "def _valid_lan_ip(raw)"}, {"doc": "Parse `ip neigh` or /proc/net/arp text into sightings.", "kind": "method", "line": 91, "name": "parse_arp_table", "signature": "def parse_arp_table(text)"}, {"doc": "Parse dnsmasq lease lines into sightings.", "kind": "method", "line": 119, "name": "parse_dhcp_leases", "signature": "def parse_dhcp_leases(text)"}, {"doc": "Parse nmap -oX output into sightings. Fail-soft, size-capped.", "kind": "method", "line": 152, "name": "parse_nmap_xml", "signature": "def parse_nmap_xml(text)"}, {"doc": "Parse nmap -oG output into sightings. Fail-soft.", "kind": "method", "line": 214, "name": "parse_nmap_grepable", "signature": "def parse_nmap_grepable(text)"}, {"doc": "Dispatch active-tool output by format, tagged with source.", "kind": "method", "line": 252, "name": "parse_active_output", "signature": "def parse_active_output(text, source)"}, {"doc": "Merge sightings by MAC, falling back to IP. Never raises.", "kind": "method", "line": 269, "name": "merge_devices", "signature": "def merge_devices(sightings)"}, {"doc": "Convert a device into graph-ready entity dicts.", "kind": "method", "line": 331, "name": "device_to_entities", "signature": "def device_to_entities(device)"}, {"doc": "Map a tool name to its contact class. Unknown means active.", "kind": "method", "line": 352, "name": "contact_for_tool", "signature": "def contact_for_tool(tool)"}, {"kind": "method", "line": 60, "name": "to_dict", "signature": "def to_dict(self)"}]}, {"doc": "estorides_core.lan_policy ========================= Authorization and contact ceiling for LAN discovery runs.  Single enforcement point for the web active-scan checkbox and the blocking consent gate. Pure: no I/O, no sockets, no subprocess.", "id": "estorides_core/lan_policy.py", "kind": "module", "label": "lan_policy.py", "language": "py", "sha256": "936c4af3f5db64c2", "symbol_count": 9, "symbols": [{"doc": "Caller intent for a LAN discovery run.", "kind": "class", "line": 30, "name": "LanPolicyRequest", "signature": "class LanPolicyRequest"}, {"doc": "Outcome of policy evaluation. JSON serializable via to_dict.", "kind": "class", "line": 40, "name": "LanPolicyDecision", "signature": "class LanPolicyDecision"}, {"doc": "Return the canonical consent notice for the blocking modal.", "kind": "method", "line": 54, "name": "requires_consent", "signature": "def requires_consent()"}, {"kind": "method", "line": 59, "name": "_env_ceiling", "signature": "def _env_ceiling()"}, {"kind": "method", "line": 66, "name": "_strict_bool", "signature": "def _strict_bool(value)"}, {"doc": "Validate scope CIDR. Returns (normalized, error_code).", "kind": "method", "line": 70, "name": "_normalize_scope", "signature": "def _normalize_scope(raw)"}, {"doc": "Return active only on the triple gate, else none.", "kind": "method", "line": 97, "name": "max_contact_for", "signature": "def max_contact_for(allow_active, consent)"}, {"doc": "Evaluate a LAN policy request. Never raises on bad input.", "kind": "method", "line": 104, "name": "evaluate", "signature": "def evaluate(request)"}, {"kind": "method", "line": 50, "name": "to_dict", "signature": "def to_dict(self)"}]}, {"doc": "estorides_core.lan_runner ========================= Real LAN scan execution: passive readers plus one gated active tool.  Readers are injectable so tests never touch the system. The active path runs only on the lan_policy triple gate through the tool_runner sandbox with a bounded timeout.", "id": "estorides_core/lan_runner.py", "kind": "module", "label": "lan_runner.py", "language": "py", "sha256": "9d813adc597a1501", "symbol_count": 8, "symbols": [{"doc": "Outcome of a LAN scan. Refusals and errors are values.", "kind": "class", "line": 41, "name": "LanScanResult", "signature": "class LanScanResult"}, {"doc": "Read the local neighbor table. Fail-soft, no probes sent.", "kind": "method", "line": 60, "name": "read_arp_table", "signature": "def read_arp_table()"}, {"doc": "Read the first readable dnsmasq lease file. Fail-soft.", "kind": "method", "line": 81, "name": "read_dhcp_leases", "signature": "def read_dhcp_leases()"}, {"kind": "method", "line": 93, "name": "_default_runner", "signature": "def _default_runner()"}, {"doc": "One sandboxed tool attempt. Stdout on success, None otherwise.", "kind": "method", "line": 99, "name": "_attempt", "signature": "def _attempt(runner, tool, args, scope_cidr, timeout)"}, {"doc": "Run gated active discovery. Returns (tool, stdout).\n\nnmap host discovery first (greppable stdout), arp-scan fallback.\n`(\"\", \"\")` when both fail. Never raises.", "kind": "method", "line": 122, "name": "run_active_scan", "signature": "def run_active_scan(scope_cidr)"}, {"doc": "Execute a LAN scan. Never raises; refusals are values.", "kind": "method", "line": 147, "name": "scan", "signature": "def scan(scope_cidr)"}, {"kind": "method", "line": 56, "name": "to_dict", "signature": "def to_dict(self)"}]}, {"doc": "estorides_core.lan_scope ======================== Private local scope auto-detection.  Pure parsing plus one thin OS reader. The reader inspects local interface configuration only; it sends no probes.", "id": "estorides_core/lan_scope.py", "kind": "module", "label": "lan_scope.py", "language": "py", "sha256": "ba8a460d9ae0efbf", "symbol_count": 5, "symbols": [{"doc": "Normalize one interface address to CIDR, or None when unsafe.", "kind": "function", "line": 23, "name": "parse_interface_addr", "signature": "def parse_interface_addr(raw)"}, {"doc": "Keep private CIDRs only, sorted and deduped. Never raises.", "kind": "function", "line": 63, "name": "select_private_cidrs", "signature": "def select_private_cidrs(interfaces)"}, {"doc": "First private CIDR, or None when no private network exists.", "kind": "function", "line": 79, "name": "default_scope", "signature": "def default_scope(interfaces)"}, {"doc": "Parse `ip addr` output into address dicts. Pure, never raises.", "kind": "function", "line": 85, "name": "parse_ip_addr_output", "signature": "def parse_ip_addr_output(text)"}, {"doc": "Read local interface addresses. Fail-soft, no probes sent.", "kind": "function", "line": 92, "name": "read_local_interfaces", "signature": "def read_local_interfaces()"}]}, {"doc": "estorides_core.lan_web ====================== Backend guard for LAN discovery runs from the web layer.  Thin mapping over lan_policy: the route hands the request body to guard() and returns the verdict status on denial. Pure, no I/O.", "id": "estorides_core/lan_web.py", "kind": "module", "label": "lan_web.py", "language": "py", "sha256": "0f068b48102f26e7", "symbol_count": 6, "symbols": [{"doc": "HTTP-ready outcome of the LAN consent gate.", "kind": "class", "line": 18, "name": "LanWebVerdict", "signature": "class LanWebVerdict"}, {"doc": "True when a run targets the local network. Never raises.", "kind": "method", "line": 50, "name": "is_lan_request", "signature": "def is_lan_request(body, query)"}, {"doc": "Build the scope response from interface addresses. Never raises.", "kind": "method", "line": 80, "name": "scope_payload", "signature": "def scope_payload(interfaces)"}, {"doc": "Validate a run request body for LAN consent. Never raises.", "kind": "method", "line": 92, "name": "guard", "signature": "def guard(body)"}, {"kind": "method", "line": 29, "name": "to_dict", "signature": "def to_dict(self)"}, {"doc": "Return (body, status) for the Flask route.", "kind": "method", "line": 32, "name": "to_response", "signature": "def to_response(self)"}]}, {"doc": "estorides_core.mitre_attack =========================== Lightweight MITRE ATT&CK mapper.  A small but curated lookup table that maps OSINT observation categories to ATT&CK technique IDs. Used to stamp every knowledge- graph observation with the techniques it might support, so the LLM analyst stage can produce a more useful brief (\"observations from sources X, Y, Z suggest technique T1595 — Active Scanning\").  The full ATT&CK STIX bundle is ~25 MB; we deliberately do NOT download it. The lookup table here covers the ~20 techniques that realistically surface in an OSINT-only context (the rest require active exploitation evidence we don't collect).  This module is a soft enhancement: missing entries are not errors, they just produce no mapping. The orchestrator can call `map_observation(observation)` after the inference step and attach the result to the observation dict.", "id": "estorides_core/mitre_attack.py", "kind": "module", "label": "mitre_attack.py", "language": "py", "sha256": "73725185924c80f3", "symbol_count": 4, "symbols": [{"doc": "Scan a text blob for ATT&CK-relevant keywords.", "kind": "function", "line": 156, "name": "_scan_keywords", "signature": "def _scan_keywords(text)"}, {"doc": "Return ATT&CK techniques associated with an observation.\n\nOutput:\n    {\n      \"techniques\": [\n        {\"id\": \"T1595\", \"label\": \"Active Scanning\", \"via\": \"source:shodan_internetdb\"},\n        ...\n      ],\n      \"tactic_ids\": [\"TA0043\"],  # not populated yet — future\n    }", "kind": "function", "line": 170, "name": "map_observation", "signature": "def map_observation(observation)"}, {"doc": "Bulk mapper. Stamps each observation in place with `_mitre` key.\n\nReturns the list of observations (for chaining). Mutates in place\nfor performance — the orchestrator doesn't keep references to the\npre-mapping list, so a side effect is safe.", "kind": "function", "line": 213, "name": "map_observations", "signature": "def map_observations(observations)"}, {"doc": "Aggregate: unique techniques across all observations, sorted by id.", "kind": "function", "line": 229, "name": "all_techniques_for", "signature": "def all_techniques_for(observations)"}]}, {"doc": "estorides_core.monitoring ========================= Watch targets and scheduling for recurring OSINT monitoring.  Provides: * WatchTarget — a recurring investigation (query + interval + alert channels) * WatchStore — SQLite-backed persistence for watch targets * WatchScheduler — manages the scheduling loop (APScheduler or simple threading) * SchedulerConfig — env-var-tunable configuration  Two modes: 1. In-process threaded scheduler (default) — simple, no external deps. 2. APScheduler — for production deployments with persistent job store.", "id": "estorides_core/monitoring.py", "kind": "module", "label": "monitoring.py", "language": "py", "sha256": "f73fb6413f3ce332", "symbol_count": 26, "symbols": [{"doc": "A recurring OSINT investigation.", "kind": "class", "line": 91, "name": "WatchTarget", "signature": "class WatchTarget"}, {"doc": "Thread-safe SQLite store for watch targets.", "kind": "class", "line": 155, "name": "WatchStore", "signature": "class WatchStore(SqliteStore)"}, {"doc": "Background thread that polls the watch store and triggers due watches.\n\nUses a simple poll loop (no external deps). For production, swap to\nAPScheduler by setting ESTORIDES_SCHEDULER_DRIVER=apscheduler.", "kind": "class", "line": 280, "name": "WatchScheduler", "signature": "class WatchScheduler"}, {"kind": "method", "line": 106, "name": "__post_init__", "signature": "def __post_init__(self)"}, {"kind": "method", "line": 110, "name": "to_dict", "signature": "def to_dict(self)"}, {"kind": "method", "line": 126, "name": "from_dict", "signature": "def from_dict(cls, d)"}, {"kind": "method", "line": 142, "name": "from_row", "signature": "def from_row(cls, row)"}, {"doc": "Persist a new watch target.", "kind": "method", "line": 162, "name": "create_watch", "signature": "def create_watch(self, watch)"}, {"kind": "method", "line": 177, "name": "get_watch", "signature": "def get_watch(self, watch_id)"}, {"kind": "method", "line": 186, "name": "update_watch", "signature": "def update_watch(self, watch)"}, {"kind": "method", "line": 199, "name": "delete_watch", "signature": "def delete_watch(self, watch_id)"}, {"kind": "method", "line": 203, "name": "list_watches", "signature": "def list_watches(self, enabled_only)"}, {"doc": "Return enabled watches whose next_run_at <= now.", "kind": "method", "line": 213, "name": "due_watches", "signature": "def due_watches(self, now)"}, {"doc": "Record a watch run start, return history entry id.", "kind": "method", "line": 226, "name": "record_run_start", "signature": "def record_run_start(self, watch_id)"}, {"kind": "method", "line": 236, "name": "record_run_complete", "signature": "def record_run_complete(self, history_id, status, entity_count, obs_count, error, alert_sent)"}, {"kind": "method", "line": 249, "name": "history", "signature": "def history(self, watch_id, limit)"}, {"kind": "method", "line": 266, "name": "stats", "signature": "def stats(self)"}, {"kind": "method", "line": 287, "name": "__init__", "signature": "def __init__(self, store, runner, alerter)"}, {"kind": "method", "line": 300, "name": "running", "signature": "def running(self)"}, {"doc": "True once an executor has been wired via set_runner().", "kind": "method", "line": 304, "name": "has_runner", "signature": "def has_runner(self)"}, {"kind": "method", "line": 308, "name": "start", "signature": "def start(self)"}, {"kind": "method", "line": 319, "name": "stop", "signature": "def stop(self)"}, {"doc": "Set the orchestrator runner function (sync or async).", "kind": "method", "line": 325, "name": "set_runner", "signature": "def set_runner(self, runner)"}, {"doc": "Set the alert dispatcher.", "kind": "method", "line": 329, "name": "set_alerter", "signature": "def set_alerter(self, alerter)"}, {"kind": "method", "line": 333, "name": "_loop", "signature": "def _loop(self)"}, {"kind": "method", "line": 344, "name": "_execute_watch", "signature": "def _execute_watch(self, watch)"}]}, {"doc": "estorides_core.observation_models ================================ Strict, versioned Pydantic v2 data contracts for the observation and entity records that flow through the engine.  The engine historically modelled these as unvalidated ``Dict[str, Any]`` built inline in the orchestrator, so a missing field, a wrong-typed value, or a hostile payload could be silently propagated into the fusion store, the knowledge graph, or the LLM analyst. These models are the schema-contract boundary: parsing a dict validates it up front, ``extra=\"forbid\"`` makes schema drift a loud, traceable failure, and ``to_legacy_dict()`` projects back to the exact shape the current in-process call sites consume.  The module is pure (no I/O, no network, no clock reads, no logging of field contents) and runs in the single-process architecture mandated by ``CLAUDE.md``: no Redis, no Celery, no FAISS, no OTLP collector.", "id": "estorides_core/observation_models.py", "kind": "module", "label": "observation_models.py", "language": "py", "sha256": "3027c2486cdba4c5", "symbol_count": 14, "symbols": [{"doc": "Return ``value`` if it is JSON-safe (None/bool/int/float/str/list/dict).\n\nRaises ``ValueError`` on ``set``, ``bytes``, and arbitrary objects so a\nhostile or structurally-invalid payload can never be retained in a\nvalidated record. Recurses into containers; dict keys must be strings.", "kind": "function", "line": 44, "name": "_check_json_safe", "signature": "def _check_json_safe(value)"}, {"doc": "Base class: strict types, forbid unknown fields, no silent coercion.", "kind": "class", "line": 66, "name": "_StrictModel", "signature": "class _StrictModel(BaseModel)"}, {"doc": "The ``meta`` sub-object of an observation (status, attempts, cache).", "kind": "class", "line": 72, "name": "ObservationMeta", "signature": "class ObservationMeta(_StrictModel)"}, {"doc": "A validated per-source observation record emitted by the engine.", "kind": "class", "line": 109, "name": "Observation", "signature": "class Observation(_StrictModel)"}, {"doc": "A validated view of a resolved entity used across the stores.", "kind": "class", "line": 147, "name": "ObservedEntity", "signature": "class ObservedEntity(_StrictModel)"}, {"doc": "The top-level validated run payload.", "kind": "class", "line": 175, "name": "RunResult", "signature": "class RunResult(_StrictModel)"}, {"kind": "method", "line": 87, "name": "_bound_url", "signature": "def _bound_url(cls, value)"}, {"kind": "method", "line": 92, "name": "_upper_method", "signature": "def _upper_method(cls, value)"}, {"kind": "method", "line": 95, "name": "to_legacy_dict", "signature": "def to_legacy_dict(self)"}, {"kind": "method", "line": 125, "name": "_json_safe", "signature": "def _json_safe(cls, value)"}, {"kind": "method", "line": 128, "name": "to_legacy_dict", "signature": "def to_legacy_dict(self)"}, {"kind": "method", "line": 160, "name": "_json_safe", "signature": "def _json_safe(cls, value)"}, {"kind": "method", "line": 163, "name": "to_legacy_dict", "signature": "def to_legacy_dict(self)"}, {"kind": "method", "line": 183, "name": "to_legacy_dict", "signature": "def to_legacy_dict(self)"}]}, {"doc": "estorides_core.ontology ======================= The \"centralized ontology engine\" — the brain of the platform.  Three things live here:  SanctionsIndex OFAC SDN list, mirrored from OpenSanctions (https://www.opensanctions.org/datasets/latest/us_ofac_sdn/targets.simple.csv). Loaded on first use, refreshed every 24h, single-flight so concurrent requests share one fetch. ~7 MB, low-tens-of-thousands of entries. In-memory graph: {entry} by id + by normalised name + by alias + by crypto address.  WikidataCache A bounded LRU over the Wikidata SPARQL endpoint. Used to canonicalise \"Vladimir Putin\" → Q7747, \"FSB\" → Q2084917, etc. 24h TTL per entry, capped at 10k items to bound memory.  OntologyEngine The orchestrator-facing facade. Exposes: - resolve(name) -> candidate sanction matches - resolve_crypto_address(addr) -> sanction cross-check - check_observation(obs) -> {hits, hits_by_field} Designed to be called from a new \"post-process\" stage that decorates each observation with a sanctions verdict before the LLM analyst sees it.  This module is deliberately dependency-light (only stdlib). The CSV", "id": "estorides_core/ontology.py", "kind": "module", "label": "ontology.py", "language": "py", "sha256": "8d3b102aa3e926ad", "symbol_count": 25, "symbols": [{"kind": "class", "line": 70, "name": "SanctionEntry", "signature": "class SanctionEntry"}, {"doc": "Lower-case, strip punctuation/diacritics, collapse whitespace.\n\nUsed for both index keys and incoming query normalisation so the\nsame string yields the same key on both sides.", "kind": "method", "line": 84, "name": "_normalise_name", "signature": "def _normalise_name(s)"}, {"doc": "In-memory OFAC SDN index with 24h lazy refresh and single-flight load.\n\nThe previous design (if any) likely fetched the list on every call\nor had no cache; the new design:\n\n  * First call to `entries()` blocks while the CSV is fetched.\n  * Subsequent calls within TTL return the cached snapshot.\n  * Concurrent calls during a load share the same in-flight\n    promise (single-flight) — never two concurrent fetches.\n  * If a refresh fails after the cache has gone stale, we keep\n    serving the previous snapshot rather than going blind.\n\nThe CSV is downloaded over HTTPS to a temp file, then loaded\ninto a normalised lookup. We never trust the file path in\nthe SDN_LOCAL_CACHE to be writable: on permission errors we\nlog a warning and fall back to in-memory only.", "kind": "class", "line": 96, "name": "SanctionsIndex", "signature": "class SanctionsIndex"}, {"doc": "Bounded LRU cache for Wikidata SPARQL queries.\n\nKeyed by `(query_kind, normalised_value)`. Values are `(fetched_at, payload)`.\nExposes `lookup_label(label)` and `lookup_org(label)` — the two\nmost common lookups in OSINT workflows.", "kind": "class", "line": 268, "name": "WikidataCache", "signature": "class WikidataCache"}, {"doc": "Public façade. Hands out the sanction index and wikidata cache.", "kind": "class", "line": 314, "name": "OntologyEngine", "signature": "class OntologyEngine"}, {"kind": "method", "line": 79, "name": "to_dict", "signature": "def to_dict(self)"}, {"kind": "method", "line": 115, "name": "__init__", "signature": "def __init__(self)"}, {"kind": "method", "line": 131, "name": "is_ready", "signature": "def is_ready(self)"}, {"doc": "Return the current snapshot, loading if necessary.", "kind": "method", "line": 134, "name": "entries", "signature": "def entries(self)"}, {"doc": "Find sanction entries whose name or alias matches `name`.", "kind": "method", "line": 141, "name": "lookup", "signature": "def lookup(self, name)"}, {"doc": "Cross-check a BTC/ETH address against the SDN list.\n\nThe OpenSanctions CSV includes a `crypto_address` (BTC) field\non a subset of entries. ETH addresses are stored in the\n`ethereum_address` field. The current implementation matches\nby alias string for simplicity — the upstream coverage of\ncrypto is sparse and not the focus of this version. Returns\nthe entries whose alias list contains the literal address.", "kind": "method", "line": 151, "name": "lookup_crypto", "signature": "def lookup_crypto(self, address)"}, {"kind": "method", "line": 168, "name": "size", "signature": "def size(self)"}, {"kind": "method", "line": 172, "name": "_refresh", "signature": "def _refresh(self)"}, {"kind": "method", "line": 201, "name": "_download", "signature": "def _download(self)"}, {"doc": "Best-effort write of the raw CSV for offline re-use.\n\nFailures are non-fatal; the in-memory index is the source of\ntruth while the process is alive.", "kind": "method", "line": 213, "name": "_persist", "signature": "def _persist(self, text)"}, {"doc": "Parse the OpenSanctions simple CSV into SanctionEntry records.", "kind": "method", "line": 226, "name": "_parse", "signature": "def _parse(self, text)"}, {"doc": "Build a normalised-name → entries lookup.", "kind": "method", "line": 255, "name": "_index", "signature": "def _index(self, entries)"}, {"kind": "method", "line": 276, "name": "__init__", "signature": "def __init__(self)"}, {"kind": "method", "line": 282, "name": "get", "signature": "def get(self, kind, value)"}, {"kind": "method", "line": 296, "name": "put", "signature": "def put(self, kind, value, payload)"}, {"kind": "method", "line": 304, "name": "stats", "signature": "def stats(self)"}, {"kind": "method", "line": 308, "name": "clear", "signature": "def clear(self)"}, {"kind": "method", "line": 317, "name": "__init__", "signature": "def __init__(self)"}, {"doc": "Run a single observation through the ontology.\n\nReturns a dict describing the sanctions verdict:\n\n  {\n    \"sanctioned\": bool,\n    \"hits\": [SanctionEntry.to_dict(), ...],\n    \"fields\": [\"registrant_name\", \"isp\", ...]  # which fields matched\n  }\n\nThe orchestrator attaches this to each observation before the\nLLM analyst stage so the system prompt can include a verdict\nline (\"SANCTIONED — OFAC SDN match on registrant\").", "kind": "method", "line": 321, "name": "check_observation", "signature": "def check_observation(self, observation)"}, {"doc": "Return (field_name, value) pairs to check against sanctions.\n\nDifferent sources expose different field names. Rather than\na giant if/elif, this is a small dispatch table — adding a\nnew source is a one-line edit.", "kind": "method", "line": 359, "name": "_candidate_fields", "signature": "def _candidate_fields(source, parsed)"}]}, {"doc": "estorides_core.openapi ====================== Minimal OpenAPI document generated from the live Flask url map.  I expose paths, methods and auth requirements without new dependencies and without duplicating the route table by hand.", "id": "estorides_core/openapi.py", "kind": "module", "label": "openapi.py", "language": "py", "sha256": "ae2772172dac6d24", "symbol_count": 1, "symbols": [{"doc": "Build an OpenAPI 3.0 document from Flask routes.", "kind": "function", "line": 14, "name": "build_openapi", "signature": "def build_openapi(app)"}]}, {"doc": "estorides_core.ops_observability ================================ Single user operational visibility without external dependencies.  I provide unauthenticated liveness, authenticated readiness and Prometheus text metrics plus an opt in JSON log formatter. I keep all counters in process behind a lock so threaded Flask workers cannot corrupt them. I sanitize every label so a hostile source name cannot inject newlines into the scrape output.", "id": "estorides_core/ops_observability.py", "kind": "module", "label": "ops_observability.py", "language": "py", "sha256": "ec053a8c11f38dc4", "symbol_count": 10, "symbols": [{"doc": "Centralized bounds for observability output.", "kind": "class", "line": 28, "name": "OpsConfig", "signature": "class OpsConfig"}, {"doc": "Return a Prometheus safe label fragment without newlines.", "kind": "method", "line": 43, "name": "_clean_label", "signature": "def _clean_label(value)"}, {"doc": "Return liveness body and status code.", "kind": "method", "line": 50, "name": "health_payload", "signature": "def health_payload()"}, {"doc": "Return readiness body and status code for registry state.", "kind": "method", "line": 55, "name": "ready_payload", "signature": "def ready_payload(source_count, sources_dir_ok)"}, {"doc": "Count one HTTP response by normalized endpoint and status.", "kind": "method", "line": 62, "name": "record_request", "signature": "def record_request(endpoint, status)"}, {"doc": "Count one source execution failure when ok is False.", "kind": "method", "line": 72, "name": "record_source", "signature": "def record_source(source, ok)"}, {"doc": "Clear all in memory counters for tests and process restart.", "kind": "method", "line": 83, "name": "reset_metrics", "signature": "def reset_metrics()"}, {"doc": "Render counters in Prometheus exposition text format.", "kind": "method", "line": 90, "name": "render_metrics", "signature": "def render_metrics()"}, {"doc": "Format one log event as JSON when opted in else legacy plain text.", "kind": "method", "line": 112, "name": "format_event", "signature": "def format_event(fields, as_json)"}, {"doc": "Expose project root without hardcoding absolute paths in callers.", "kind": "method", "line": 130, "name": "project_root", "signature": "def project_root()"}]}, {"doc": "estorides_core.orchestrator ============================ The main entry point. Glues together: * source registry  (source_loader) * async http       (async_client) * parsers          (parsers) * entity extract   (entity_extraction) * knowledge graph  (knowledge_graph) * LLM manager      (llm.manager) * dataset export   (exporters)  Public surface: orchestrator = Orchestrator() await orchestrator.run(query, ...)", "id": "estorides_core/orchestrator.py", "kind": "module", "label": "orchestrator.py", "language": "py", "sha256": "69aa7cad42cfa087", "symbol_count": 18, "symbols": [{"doc": "Number of slow CLI-tool runs still in flight (for the stream gate).", "kind": "function", "line": 56, "name": "pending_system_app_tasks", "signature": "def pending_system_app_tasks()"}, {"doc": "Format a string template with {key} placeholders.\n\nNon-string values (int, list, dict) are returned as-is so YAMLs that\nuse raw integers or list literals in params/body don't blow up.", "kind": "function", "line": 115, "name": "_safe_format", "signature": "def _safe_format(template)"}, {"doc": "Look up the API key for a source that needs one.", "kind": "function", "line": 127, "name": "_resolve_auth", "signature": "def _resolve_auth(source)"}, {"doc": "Return the set of applies_to tags a query_type matches.", "kind": "function", "line": 152, "name": "_expand_query_type", "signature": "def _expand_query_type(query_type)"}, {"doc": "Heuristic: if the query looks like a domain, return it; if it's an IP, return None.", "kind": "function", "line": 158, "name": "_domain_from_query", "signature": "def _domain_from_query(q)"}, {"kind": "class", "line": 168, "name": "Orchestrator", "signature": "class Orchestrator"}, {"kind": "method", "line": 122, "name": "repl", "signature": "def repl(m)"}, {"kind": "method", "line": 169, "name": "__init__", "signature": "def __init__(self, registry, llm, kg)"}, {"doc": "Run a full intelligence cycle. Returns a structured result.\n\n`deadline` is a hard wall-clock cap (seconds) for the whole fanout.\nAny source that hasn't responded by then is dropped and reported as\n\"deadline_exceeded\" so the run can never get stuck.\n\n`persist` (default True) writes the run to the persistent case\nstore and mirrors every entity/edge to the Kùzu graph. Set\nFalse for one-off ad-hoc queries (e.g. tests) where you don't\nwant the run to bloat the long-term memory.\n\n`case_id`, when supplied, makes this run append to an existing\ncase instead of opening a new one. The recursive pivot engine\nuses this so every hop of a cross-search lands in a single case.\n\n`on_source_result`, when supplied, is invoked with the shaped\nobservation dict the moment each source resolves, before the\nfan-out as a whole completes. This is what lets the UI populate\nprogressively instead of blocking on the slowest source.\n\n`passive_only` (default False) restricts the fan-out to sources\nwhose contact class is `none` — the target's own infrastructure is\nnever touched, not even by a third-party broker probe. Use this for\nbug-bounty scoping where a probe attributable to the operator's\nrecon window must not appear in the target's logs.\n\n`proxy`, when supplied (e.g. `socks5://127.0.0.1:9050` for Tor),\nroutes every outbound request through it so the queried brokers\nnever see the operator's real egress IP. Falls back to the\nenv-configured proxy pool when omitted.", "kind": "method", "line": 190, "name": "run", "signature": "def run(self, query)"}, {"kind": "method", "line": 671, "name": "_select_sources", "signature": "def _select_sources(self, names)"}, {"doc": "Dispatch a source to its execution strategy.", "kind": "method", "line": 707, "name": "_execute_source", "signature": "def _execute_source(self, client, source, query, on_done, on_result)"}, {"doc": "Sweep gather output into uniform 4-tuples.\n\nEach successful task already returns its own originating source\nas element zero, so alignment never depends on positional zip\nagainst the target list. The zip fallback only supplies a source\nname for exceptions and bad shapes.", "kind": "method", "line": 724, "name": "_normalise_results", "signature": "def _normalise_results(self, raw_results, targets)"}, {"doc": "Best-effort source for a broken result: positional fallback.", "kind": "method", "line": 764, "name": "_source_of", "signature": "def _source_of(item, fallback, raw)"}, {"doc": "Run a `system_app` source through the tool_runner sandbox.", "kind": "method", "line": 774, "name": "_run_system_app", "signature": "def _run_system_app(self, source, query, on_done, on_result)"}, {"kind": "method", "line": 841, "name": "_run_http_source", "signature": "def _run_http_source(self, client, source, query, on_done, on_result)"}, {"doc": "Extract the next-page cursor from a parsed response body.", "kind": "method", "line": 953, "name": "_extract_cursor", "signature": "def _extract_cursor(data, cfg)"}, {"doc": "Delegate each observation to its registered inferer.\n\nThe previous version of this method was a 90-line `if/elif`\nchain hard-coded to specific source names. The new version\nwalks the inferer registry; sources with no inferer are\nsilently skipped. Adding a new inferer is now: write a\nfunction and `@register_inferer(\"source_name\")` it. No edits\nto this method.", "kind": "method", "line": 958, "name": "_infer_relationships", "signature": "def _infer_relationships(self, observations, query)"}, {"kind": "method", "line": 971, "name": "_write_dataset", "signature": "def _write_dataset(self, query, observations, entities, analysis)"}]}, {"doc": "estorides_core.osiris_sources ============================= Extra OSINT endpoints borrowed from the Osiris project (simplifaisoul/osiris). Each function returns a Flask Response-ready dict; the Blueprint in `estorides_web` wires them up to public routes.  Why a separate module: The main 99 sources are YAML-defined and go through the orchestrator pipeline (parser → entity extraction → KG). The Osiris-style endpoints are narrower: they hit a single free API and return a flat JSON response without going through the orchestrator. Mixing them into the YAML registry would force a parallel \"raw passthrough\" parser and bloat `parsers.py`. Keeping them here makes the boundary explicit and easy to audit.  Source map (Osiris → here): /api/osiris/bgp        bgpview.io (keyless) /api/osiris/mac        macvendors.co (keyless) /api/osiris/phone      local libphonenumber + region centroid table /api/osiris/github     api.github.com (keyless, rate-limited) /api/osiris/leaks      api.xposedornot.com (keyless, more detail than HIBP) /api/osiris/cisa-kev   cisa.gov KEV JSON feed /api/osiris/malware    feodotracker.abuse.ch + urlhaus-api.abuse.ch", "id": "estorides_core/osiris_sources.py", "kind": "module", "label": "osiris_sources.py", "language": "py", "sha256": "95e0254a3a6e1b70", "symbol_count": 8, "symbols": [{"doc": "GET with a small on-disk JSON cache. Returns parsed JSON or None.\n\nUsed for the slow-changing feeds (CISA KEV, malware lists) so a\nburst of operator clicks doesn't hammer the upstream.", "kind": "function", "line": 81, "name": "_cached_get", "signature": "def _cached_get(url)"}, {"doc": "Look up an IP or AS number against bgpview.io (free, no key).", "kind": "function", "line": 119, "name": "fetch_bgp", "signature": "def fetch_bgp(query)"}, {"doc": "Look up a MAC address against macvendors.co (free, no key).", "kind": "function", "line": 185, "name": "fetch_mac", "signature": "def fetch_mac(mac)"}, {"doc": "Best-effort phone geolocation.\n\nThe implementation is intentionally simple (regex + region\ntable). It does NOT replace libphonenumber; it provides a\nsame-shape response without the dep so the API stays consistent\nwith Osiris' /api/osint/phone.", "kind": "function", "line": 233, "name": "fetch_phone", "signature": "def fetch_phone(number)"}, {"doc": "Look up a GitHub user (keyless, rate-limited).", "kind": "function", "line": 302, "name": "fetch_github_user", "signature": "def fetch_github_user(username)"}, {"doc": "Breach analytics for `email` via xposedornot (free, no key).", "kind": "function", "line": 356, "name": "fetch_leaks", "signature": "def fetch_leaks(email)"}, {"doc": "Recently-added CVEs from the CISA KEV feed (authoritative).", "kind": "function", "line": 400, "name": "fetch_cisa_kev", "signature": "def fetch_cisa_kev(limit, days)"}, {"doc": "Active botnet C2 (Feodo) + recent malware URLs (URLhaus).\n\nBoth abuse.ch, both keyless. Each entry is geolocated against the\ncountry centroid table with deterministic jitter so multiple\nthreats in the same country don't stack on the same pixel.", "kind": "function", "line": 452, "name": "fetch_malware_c2", "signature": "def fetch_malware_c2(limit)"}]}, {"doc": "estorides_core.pagination ========================= Pagination strategies for source API fetches.  Pure dataclasses + iteration logic. No I/O, no asyncio, no HTTP. The orchestrator owns the fetch loop; this module only describes *how* to advance from one page to the next and *when* to stop.", "id": "estorides_core/pagination.py", "kind": "module", "label": "pagination.py", "language": "py", "sha256": "0d9527e09c37f8ee", "symbol_count": 7, "symbols": [{"doc": "Configuration for a paginated source fetch.\n\nParsed from the source YAML's ``pagination`` key. No fields are\nrequired; a source with no pagination config results in a single\nfetch (the current default behaviour).", "kind": "class", "line": 19, "name": "PaginationConfig", "signature": "class PaginationConfig"}, {"doc": "Build URL params dict for a given page number.\n\nReturns an empty dict for cursor strategy (the cursor is set\ndynamically from the response) or when pagination is disabled.", "kind": "method", "line": 62, "name": "build_page_params", "signature": "def build_page_params(cfg, page_num)"}, {"doc": "Extract the next-page cursor from a parsed response body.\n\nWalks the dot-separated ``cursor_path`` into the JSON-like dict.\nReturns ``None`` when the path is absent or the value is empty.", "kind": "method", "line": 78, "name": "extract_cursor", "signature": "def extract_cursor(data, cfg)"}, {"doc": "Count results in a parsed response page.\n\nUses ``response_list_path`` if configured, otherwise tries common\nJSON fields (``results``, ``items``, ``data``) or falls back to\n``len(data)`` for a list-type response.", "kind": "method", "line": 99, "name": "count_results", "signature": "def count_results(data, cfg)"}, {"kind": "method", "line": 38, "name": "from_dict", "signature": "def from_dict(raw)"}, {"kind": "method", "line": 54, "name": "enabled", "signature": "def enabled(self)"}, {"kind": "method", "line": 58, "name": "needs_page_size", "signature": "def needs_page_size(self)"}]}, {"doc": "estorides_core.parsers ====================== A small library of structured parsers. Each parser knows how to take a raw response from a specific OSINT source and pull out the things the rest of the pipeline cares about: domain, IP, geolocation, etc.  The `parser` field of each YAML source selects one of these functions via the `PARSERS` registry. New parsers are added with `@register_parser(\"name\")` or by appending to `PARSERS` directly.", "id": "estorides_core/parsers.py", "kind": "module", "label": "parsers.py", "language": "py", "sha256": "68577252a81fa5b8", "symbol_count": 65, "symbols": [{"doc": "Return `obj` as a dict, or an empty dict when it is anything else.\n\nRemote JSON frequently puts `null`/a string where a nested object is\nexpected. Coercing here keeps every parser total (the module contract).", "kind": "function", "line": 31, "name": "_d", "signature": "def _d(obj)"}, {"doc": "Return the first dict element of `items`, else an empty dict.", "kind": "function", "line": 40, "name": "_first_dict", "signature": "def _first_dict(items)"}, {"doc": "Return `obj` as a list, or [] when it is anything else.\n\nGuards the very common `...get(\"items\") or []` idiom, which is NOT\nsafe against a truthy non-iterable (a remote `\"items\": true`).", "kind": "function", "line": 47, "name": "_list", "signature": "def _list(obj)"}, {"doc": "Return `obj` as a string, or \"\" when it is anything else.", "kind": "function", "line": 56, "name": "_text", "signature": "def _text(obj)"}, {"doc": "Google/Cloudflare DNS-over-HTTPS response.", "kind": "function", "line": 62, "name": "parse_dns_json", "signature": "def parse_dns_json(payload)"}, {"doc": "crt.sh CT log response.", "kind": "function", "line": 78, "name": "parse_crtsh_json", "signature": "def parse_crtsh_json(payload)"}, {"doc": "RDAP (RFC 7483) domain object.\n\nReturns a flat dict with registrar, registry handle, status flags,\nevent dates, and any nameserver / entity hints. The structured\nresult feeds two goals:\n\n  1. Surface the registrar as an `entity` so the resolver can\n     later ask \"which other domains does MarkMonitor manage\"\n     (or whichever registrar came back) and fan out into the\n     shared-infrastructure lane.\n  2. Save the create / expire / updated dates so the timeline\n     view can render them without a second pass.\n\nDefensive against missing fields — RDAP responses vary across\nregistries and the spec allows a lot of optional bits.", "kind": "function", "line": 96, "name": "parse_rdap", "signature": "def parse_rdap(payload)"}, {"doc": "ip-api.com response.", "kind": "function", "line": 176, "name": "parse_ipapi", "signature": "def parse_ipapi(payload)"}, {"kind": "function", "line": 202, "name": "parse_ipinfo", "signature": "def parse_ipinfo(payload)"}, {"kind": "function", "line": 217, "name": "parse_ipapi_co", "signature": "def parse_ipapi_co(payload)"}, {"doc": "internetdb.shodan.io — IP service summary.", "kind": "function", "line": 227, "name": "parse_shodan_internetdb", "signature": "def parse_shodan_internetdb(payload)"}, {"kind": "function", "line": 241, "name": "parse_greynoise", "signature": "def parse_greynoise(payload)"}, {"kind": "function", "line": 256, "name": "parse_ipwhois", "signature": "def parse_ipwhois(payload)"}, {"kind": "function", "line": 274, "name": "parse_abuseipdb", "signature": "def parse_abuseipdb(payload)"}, {"doc": "Flatten VirusTotal v3 last_analysis_stats into a compact dict.", "kind": "function", "line": 290, "name": "_vt_stats", "signature": "def _vt_stats(attrs)"}, {"doc": "VirusTotal v3 — IP address object.", "kind": "function", "line": 304, "name": "parse_vt_ip", "signature": "def parse_vt_ip(payload)"}, {"doc": "VirusTotal v3 — domain object.", "kind": "function", "line": 325, "name": "parse_vt_domain", "signature": "def parse_vt_domain(payload)"}, {"doc": "VirusTotal v3 — file object.", "kind": "function", "line": 352, "name": "parse_vt_file", "signature": "def parse_vt_file(payload)"}, {"doc": "BGPView IP/ASN response (keyless). Total: never raises.", "kind": "function", "line": 376, "name": "parse_bgpview", "signature": "def parse_bgpview(payload)"}, {"doc": "CISA KEV catalog (keyless). Capped at 20 items. Total: never raises.", "kind": "function", "line": 408, "name": "parse_cisa_kev", "signature": "def parse_cisa_kev(payload)"}, {"kind": "function", "line": 427, "name": "parse_ripe_stat", "signature": "def parse_ripe_stat(payload)"}, {"kind": "function", "line": 438, "name": "parse_nominatim", "signature": "def parse_nominatim(payload)"}, {"kind": "function", "line": 456, "name": "parse_urlscan", "signature": "def parse_urlscan(payload)"}, {"doc": "CDX returns a list where the first row is the header.", "kind": "function", "line": 478, "name": "parse_wayback_cdx", "signature": "def parse_wayback_cdx(payload)"}, {"kind": "function", "line": 493, "name": "parse_wayback_avail", "signature": "def parse_wayback_avail(payload)"}, {"kind": "function", "line": 503, "name": "parse_threatfox", "signature": "def parse_threatfox(payload)"}, {"kind": "function", "line": 512, "name": "parse_urlhaus", "signature": "def parse_urlhaus(payload)"}, {"kind": "function", "line": 521, "name": "parse_urlhaus_payloads", "signature": "def parse_urlhaus_payloads(payload)"}, {"kind": "function", "line": 530, "name": "parse_malwarebazaar", "signature": "def parse_malwarebazaar(payload)"}, {"kind": "function", "line": 539, "name": "parse_otx", "signature": "def parse_otx(payload)"}, {"kind": "function", "line": 564, "name": "parse_hibp_breach", "signature": "def parse_hibp_breach(payload)"}, {"kind": "function", "line": 582, "name": "parse_hibp_paste", "signature": "def parse_hibp_paste(payload)"}, {"kind": "function", "line": 598, "name": "parse_phonebook", "signature": "def parse_phonebook(payload)"}, {"kind": "function", "line": 619, "name": "parse_wikipedia", "signature": "def parse_wikipedia(payload)"}, {"kind": "function", "line": 628, "name": "parse_wikidata", "signature": "def parse_wikidata(payload)"}, {"kind": "function", "line": 640, "name": "parse_openalex", "signature": "def parse_openalex(payload)"}, {"kind": "function", "line": 665, "name": "parse_crossref", "signature": "def parse_crossref(payload)"}, {"doc": "arXiv returns Atom XML; we expect callers to have converted to a dict.", "kind": "function", "line": 685, "name": "parse_arxiv", "signature": "def parse_arxiv(payload)"}, {"kind": "function", "line": 706, "name": "parse_nvd_cve", "signature": "def parse_nvd_cve(payload)"}, {"kind": "function", "line": 727, "name": "parse_github_advisories", "signature": "def parse_github_advisories(payload)"}, {"kind": "function", "line": 752, "name": "parse_blockchain_btc", "signature": "def parse_blockchain_btc(payload)"}, {"kind": "function", "line": 769, "name": "parse_blockstream", "signature": "def parse_blockstream(payload)"}, {"kind": "function", "line": 785, "name": "parse_ethplorer", "signature": "def parse_ethplorer(payload)"}, {"kind": "function", "line": 802, "name": "parse_microlink", "signature": "def parse_microlink(payload)"}, {"kind": "function", "line": 822, "name": "parse_github_user", "signature": "def parse_github_user(payload)"}, {"kind": "function", "line": 842, "name": "parse_github_search", "signature": "def parse_github_search(payload)"}, {"kind": "function", "line": 857, "name": "parse_reddit", "signature": "def parse_reddit(payload)"}, {"kind": "function", "line": 887, "name": "parse_mastodon", "signature": "def parse_mastodon(payload)"}, {"kind": "function", "line": 903, "name": "parse_keybase", "signature": "def parse_keybase(payload)"}, {"kind": "function", "line": 932, "name": "parse_hackernews", "signature": "def parse_hackernews(payload)"}, {"kind": "function", "line": 944, "name": "parse_reddit_search", "signature": "def parse_reddit_search(payload)"}, {"kind": "function", "line": 958, "name": "parse_dev_to", "signature": "def parse_dev_to(payload)"}, {"doc": "Generic: split raw_text by newlines, drop empties.", "kind": "function", "line": 973, "name": "parse_text_lines", "signature": "def parse_text_lines(payload)"}, {"kind": "function", "line": 984, "name": "parse_raw_text", "signature": "def parse_raw_text(payload)"}, {"doc": "hackertarget returns text; expect a one-line-per-header response.", "kind": "function", "line": 992, "name": "parse_http_headers", "signature": "def parse_http_headers(payload)"}, {"kind": "function", "line": 1008, "name": "parse_whois_text", "signature": "def parse_whois_text(payload)"}, {"doc": "Twitter/X API v2 user by username.\n\nExpects the v2 JSON shape: {\"data\": {\"id\": ..., \"name\": ..., \"username\": ..., ...}}\nReturns flat dict with the core profile fields.", "kind": "function", "line": 1026, "name": "parse_twitter_user", "signature": "def parse_twitter_user(payload)"}, {"doc": "YouTube Data API v3 channel by handle.\n\nExpects: {\"items\": [{\"id\": ..., \"snippet\": {...}, \"statistics\": {...}}]}\nReturns a flat profile dict with channel metadata.", "kind": "function", "line": 1062, "name": "parse_youtube_user", "signature": "def parse_youtube_user(payload)"}, {"doc": "Twitch Helix API user by login.\n\nExpects: {\"data\": [{\"id\": ..., \"login\": ..., \"display_name\": ..., ...}]}\nReturns a flat profile dict with core Twitch metadata.", "kind": "function", "line": 1099, "name": "parse_twitch_user", "signature": "def parse_twitch_user(payload)"}, {"doc": "Discord server discovery via discords.com API.\n\nExpected shape: a list of server dicts with name, description,\nmember_count, etc. Or a dict with a 'results' or 'servers' key.", "kind": "function", "line": 1132, "name": "parse_discord_discovery", "signature": "def parse_discord_discovery(payload)"}, {"doc": "Normalise a single raw Discord server dict into a standard shape.", "kind": "function", "line": 1152, "name": "_normalise_discord_server", "signature": "def _normalise_discord_server(raw)"}, {"doc": "Return the parser function for `name`, or a passthrough lambda.\n\nUnknown parser names deliberately fall through to `parse_raw_text`\nso a source YAML with a typo in the `parser` field never crashes\nthe run — it just produces a less-structured observation.", "kind": "function", "line": 1251, "name": "get_parser", "signature": "def get_parser(name)"}, {"doc": "Decorator: register `func` as a parser under `name`.\n\nUsed by addon authors and tests to extend the catalog without\ntouching the central `PARSERS` dict. Idempotent: re-registering\nthe same name overwrites the previous entry, with a debug log so\na typo doesn't silently drop a parser.", "kind": "function", "line": 1264, "name": "register_parser", "signature": "def register_parser(name, description)"}, {"doc": "Return (name, description) tuples for every registered parser.\n\nUsed by the CLI `status` endpoint to advertise the available\nparser names, and by tests to assert that a custom parser made it\ninto the registry.", "kind": "function", "line": 1280, "name": "list_parsers", "signature": "def list_parsers()"}, {"kind": "function", "line": 1272, "name": "deco", "signature": "def deco(func)"}]}, {"id": "estorides_core/pdns_monitor.py", "kind": "module", "label": "pdns_monitor.py", "language": "py", "sha256": "366f171e4f588e73", "symbol_count": 11, "symbols": [{"kind": "class", "line": 13, "name": "HistoricalSubdomain", "signature": "class HistoricalSubdomain"}, {"kind": "class", "line": 27, "name": "IPRecord", "signature": "class IPRecord"}, {"kind": "class", "line": 40, "name": "CertRecord", "signature": "class CertRecord"}, {"kind": "class", "line": 55, "name": "PDNSResult", "signature": "class PDNSResult"}, {"kind": "method", "line": 72, "name": "classify_subdomain_status", "signature": "def classify_subdomain_status(fqdn, resolved_ips)"}, {"kind": "method", "line": 76, "name": "extract_sans_from_cert", "signature": "def extract_sans_from_cert(cert)"}, {"kind": "method", "line": 80, "name": "analyse_pdns_data", "signature": "def analyse_pdns_data(subdomains, ip_history, new_certs)"}, {"kind": "method", "line": 22, "name": "to_dict", "signature": "def to_dict(self)"}, {"kind": "method", "line": 35, "name": "to_dict", "signature": "def to_dict(self)"}, {"kind": "method", "line": 50, "name": "to_dict", "signature": "def to_dict(self)"}, {"kind": "method", "line": 62, "name": "to_dict", "signature": "def to_dict(self)"}]}, {"id": "estorides_core/people_intel.py", "kind": "module", "label": "people_intel.py", "language": "py", "sha256": "661cdbaaf42ae7be", "symbol_count": 13, "symbols": [{"kind": "class", "line": 15, "name": "BreachRecord", "signature": "class BreachRecord"}, {"kind": "class", "line": 29, "name": "Employee", "signature": "class Employee"}, {"kind": "class", "line": 56, "name": "BreachContext", "signature": "class BreachContext"}, {"kind": "class", "line": 67, "name": "PeopleIntelResult", "signature": "class PeopleIntelResult"}, {"kind": "method", "line": 98, "name": "infer_email_pattern", "signature": "def infer_email_pattern(emails)"}, {"kind": "method", "line": 136, "name": "_match_pattern", "signature": "def _match_pattern(local)"}, {"kind": "method", "line": 143, "name": "_severity_from_breaches", "signature": "def _severity_from_breaches(breaches)"}, {"kind": "method", "line": 153, "name": "correlate_breaches", "signature": "def correlate_breaches(employees)"}, {"kind": "method", "line": 176, "name": "analyse_employees", "signature": "def analyse_employees(employees, domain)"}, {"kind": "method", "line": 22, "name": "to_dict", "signature": "def to_dict(self)"}, {"kind": "method", "line": 40, "name": "to_dict", "signature": "def to_dict(self)"}, {"kind": "method", "line": 62, "name": "to_dict", "signature": "def to_dict(self)"}, {"kind": "method", "line": 75, "name": "to_dict", "signature": "def to_dict(self)"}]}, {"doc": "estorides_core.pivot_engine =========================== Recursive, asynchronous cross-search engine — the Palantir-style core.  Given a seed selector (a domain, IP, email, ASN, wallet), the engine runs the full OSINT fan-out, harvests the selectors that surface, scores them, and re-queries the highest-value ones, hop by hop, until a bounded budget is spent. Every hop appends to a single case so the cross-search reads as one coherent investigation.  The design is deliberately decoupled (SOLID):  * `EntityRunner`  — an abstraction over \"run one target, give me the observations and entities\". Satisfied by the existing Orchestrator. * `EventSink`     — an abstraction over \"publish a progress event\". The SSE layer, a test list, or a log adapter all satisfy it. The engine knows nothing about HTTP. * `PivotPolicyConfig` (in config) — decides which selectors pivot and how leads are scored. Swapping the policy swaps the behaviour without touching the engine (open/closed). * `PivotConfig` (in config) — every bound and default. No literal in this module is a tunable number.  The frontier is a max-heap by lead score, so a high-signal selector (an email, a wallet) expands before low-signal shared infrastructure at the same depth. Confidence decays with depth and is carried on every emitted entity so a downstream consumer can rank or prune.", "id": "estorides_core/pivot_engine.py", "kind": "module", "label": "pivot_engine.py", "language": "py", "sha256": "2d16363456eb41fc", "symbol_count": 25, "symbols": [{"doc": "A single, transport-neutral progress event.\n\n`type` is a stable vocabulary token; `data` is a JSON-serialisable\npayload. Sinks translate these to their own wire format.", "kind": "class", "line": 47, "name": "PivotEvent", "signature": "class PivotEvent"}, {"doc": "Receives engine progress events. Implementations must not raise.", "kind": "class", "line": 59, "name": "EventSink", "signature": "class EventSink(Protocol)"}, {"doc": "In-memory sink. Useful for tests and synchronous embedding.", "kind": "class", "line": 67, "name": "ListEventSink", "signature": "class ListEventSink"}, {"doc": "Bounded sink that flattens events to JSON-ready dicts for SSE drain.\n\nEach stored item is `{\"type\": ..., \"ts\": ..., **data}`. When the buffer\nexceeds `capacity` the oldest items are dropped and a `heartbeat` marker\nrecords how many were lost, so a slow client degrades gracefully instead\nof stalling the producer. The terminal `finished`/`fatal` event also\nflips `done` so a poller knows when to stop without parsing payloads.", "kind": "class", "line": 77, "name": "BufferedEventSink", "signature": "class BufferedEventSink"}, {"doc": "Runs the OSINT fan-out for a single target.\n\nThe engine depends on this narrow port rather than the concrete\nOrchestrator, so it can be driven by a stub in tests.", "kind": "class", "line": 113, "name": "EntityRunner", "signature": "class EntityRunner(Protocol)"}, {"doc": "Mutable accounting for one cross-search.\n\nHolds the three hard ceilings (steps, entities, wall-clock) and the\nmonotonic clock the deadline is measured against. `exhausted()` returns\na human reason string the moment any ceiling is hit, else None.", "kind": "class", "line": 139, "name": "PivotBudget", "signature": "class PivotBudget"}, {"doc": "A target waiting in the frontier.", "kind": "class", "line": 172, "name": "PivotLead", "signature": "class PivotLead"}, {"doc": "Terminal summary of a completed cross-search.", "kind": "class", "line": 184, "name": "PivotResult", "signature": "class PivotResult"}, {"doc": "Drives the recursive, scored, asynchronous cross-search.", "kind": "class", "line": 195, "name": "PivotEngine", "signature": "class PivotEngine"}, {"doc": "Publish one event. A slow or failing sink must never break a run.", "kind": "method", "line": 62, "name": "emit", "signature": "def emit(self, event)"}, {"kind": "method", "line": 70, "name": "__init__", "signature": "def __init__(self)"}, {"kind": "method", "line": 73, "name": "emit", "signature": "def emit(self, event)"}, {"kind": "method", "line": 87, "name": "__init__", "signature": "def __init__(self, capacity)"}, {"kind": "method", "line": 94, "name": "emit", "signature": "def emit(self, event)"}, {"kind": "method", "line": 120, "name": "run", "signature": "def run(self, query)"}, {"doc": "Seconds remaining before the global wall-clock deadline.", "kind": "method", "line": 155, "name": "time_left", "signature": "def time_left(self)"}, {"doc": "Reason the run must stop, or None while budget remains.", "kind": "method", "line": 159, "name": "exhausted", "signature": "def exhausted(self)"}, {"kind": "method", "line": 198, "name": "__init__", "signature": "def __init__(self, runner, sink)"}, {"doc": "Build and publish an event, swallowing any sink failure.", "kind": "method", "line": 246, "name": "_emit", "signature": "def _emit(self, event_type)"}, {"doc": "Push a lead as a max-heap by score (negated for heapq).", "kind": "method", "line": 255, "name": "_heap_push", "signature": "def _heap_push(heap, counter, lead)"}, {"doc": "Execute the cross-search from `(seed_type, seed_value)`.\n\nReturns a `PivotResult`. Progress is published incrementally\nthrough the injected sink while the coroutine is in flight.", "kind": "method", "line": 264, "name": "run", "signature": "def run(self, seed_type, seed_value)"}, {"doc": "Run the fan-out for one lead and enqueue its scored children.", "kind": "method", "line": 341, "name": "_expand_lead", "signature": "def _expand_lead(self, lead, frontier, budget)"}, {"doc": "Score the entities a target produced and enqueue the best ones.\n\nReturns the number of leads actually added to the frontier. The\nper-step breadth cap keeps a single popular target (a CDN, a\nregistrar) from flooding the queue.", "kind": "method", "line": 411, "name": "_ingest_children", "signature": "def _ingest_children(self, parent, result, frontier, budget)"}, {"kind": "method", "line": 356, "name": "_on_source_done", "signature": "def _on_source_done(name, ok, status, elapsed_ms)"}, {"kind": "method", "line": 366, "name": "_on_source_result", "signature": "def _on_source_result(observation)"}]}, {"doc": "estorides_core.recon_fusion =========================== Passive reconnaissance fusion engine: groups, deduplicates and classifies OSINT results by relevance tier so the operator sees the most useful information first.  Pure transformation: List[Observation] + List[Entity] + Query -> FusionResult.", "id": "estorides_core/recon_fusion.py", "kind": "module", "label": "recon_fusion.py", "language": "py", "sha256": "0483926506b8384d", "symbol_count": 19, "symbols": [{"doc": "Relevance classification for grouped reconnaissance results.\n\nOrdered from most to least relevant for UI rendering.", "kind": "class", "line": 27, "name": "RelevanceTier", "signature": "class RelevanceTier(str, Enum)"}, {"doc": "One canonical entity grouped across all sources that observed it.", "kind": "class", "line": 46, "name": "GroupedEntity", "signature": "class GroupedEntity"}, {"doc": "Complete output of the recon fusion engine.", "kind": "class", "line": 84, "name": "FusionResult", "signature": "class FusionResult"}, {"doc": "Deterministic normalisation matching fusion_store.entity_id.", "kind": "method", "line": 107, "name": "_normalize_value", "signature": "def _normalize_value(etype, value)"}, {"doc": "Deterministic sha1-based entity id matching fusion_store convention.", "kind": "method", "line": 112, "name": "_canonical_id", "signature": "def _canonical_id(etype, value)"}, {"doc": "Logarithmic corroboration weight: min(1, log10(1 + n)).", "kind": "method", "line": 117, "name": "_corroboration_factor", "signature": "def _corroboration_factor(source_count)"}, {"doc": "Linear freshness decay from 1.0 (fresh) to 0.1 (stale).", "kind": "method", "line": 124, "name": "_freshness_factor", "signature": "def _freshness_factor(age_hours, max_hours)"}, {"doc": "True if the entity value matches the original query.", "kind": "method", "line": 132, "name": "_direct_match_query", "signature": "def _direct_match_query(value, query)"}, {"doc": "Extract textual key findings from a list of observations.", "kind": "method", "line": 137, "name": "_extract_key_findings", "signature": "def _extract_key_findings(observations)"}, {"doc": "Stateless engine that classifies raw OSINT results into relevance tiers.\n\nThread-safe (no mutable state). Reusable across requests.", "kind": "class", "line": 160, "name": "ReconFusionEngine", "signature": "class ReconFusionEngine"}, {"doc": "Return tiers in canonical display order.", "kind": "method", "line": 40, "name": "ordered", "signature": "def ordered(cls)"}, {"kind": "method", "line": 64, "name": "to_dict", "signature": "def to_dict(self)"}, {"kind": "method", "line": 95, "name": "to_dict", "signature": "def to_dict(self)"}, {"kind": "method", "line": 166, "name": "__init__", "signature": "def __init__(self, config)"}, {"doc": "Classify raw observations and entities into relevance-tiered groups.\n\nArgs:\n    query: The original operator query (non-empty).\n    query_type: Detected type of the query.\n    observations: Raw observation dicts from the orchestrator.\n    entities: Raw entity dicts from entity extraction.\n\nReturns:\n    FusionResult with tiered, grouped, deduplicated results.\n\nRaises:\n    ValueError: If query is empty.", "kind": "method", "line": 169, "name": "classify", "signature": "def classify(self, query, query_type, observations, entities)"}, {"doc": "Remove exact duplicates based on config dedup keys.\n\nTwo observations are identical if they share the same values for all\nexact_dedup_keys. The first occurrence is kept.", "kind": "method", "line": 219, "name": "_deduplicate", "signature": "def _deduplicate(self, observations)"}, {"doc": "Group observations and entities by canonical entity id.\n\nPrimary grouping key is the explicit entity list. Observations are\nattached to groups when the entity value appears in the observation's\nparsed data or the observation source matches the entity's sources.", "kind": "method", "line": 237, "name": "_group_by_entity", "signature": "def _group_by_entity(self, observations, entities)"}, {"doc": "Assign each group a relevance tier and score.", "kind": "method", "line": 321, "name": "_classify_groups", "signature": "def _classify_groups(self, groups, query)"}, {"doc": "Determine the relevance tier based on source count and reliability.\n\nThresholds:\n    CRITICAL: source_count >= critical_min_sources (3) OR\n              (source_count >= 2 AND avg_reliability >= B weight)\n    HIGH:     source_count >= high_min_sources (2) OR\n              (source_count >= 1 AND avg_reliability >= high_min_reliability AND direct_match)\n    MEDIUM:   source_count >= 1 AND avg_reliability > noise threshold\n    LOW:      source_count >= 1 AND avg_reliability <= noise threshold\n    NOISE:    source_count >= 1 AND avg_reliability <= F weight\n              OR no corroboration from untrusted source", "kind": "method", "line": 386, "name": "_assign_tier", "signature": "def _assign_tier(self, source_count, avg_reliability, direct_match)"}]}, {"id": "estorides_core/recon_pipeline.py", "kind": "module", "label": "recon_pipeline.py", "language": "py", "sha256": "cbbbe43fb4bee5b1", "symbol_count": 1, "symbols": [{"kind": "function", "line": 22, "name": "run_passive_recon", "signature": "def run_passive_recon(query, headers, html, cookies, employees, code_findings, third_parties, pdns_subdomains, cloud_assets)"}]}, {"doc": "estorides_core.relationship_inference ===================================== Strategy registry for deriving higher-level knowledge-graph edges from raw observations.  The orchestrator's old `_infer_relationships()` was a chain of hard-coded `if src == \"foo\": ...` blocks — every new source that wanted to contribute edges meant editing the central orchestrator. This module replaces that with a registry: a `RelationshipInferer` is a small callable that knows how to translate one observation into graph edges, and the orchestrator walks the registry.  Add a new inferer by: 1. Writing a function `def my_inferer(observation, query, kg) -> None` 2. Decorating it with `@register_inferer(\"source_name\")`  The orchestrator picks the right inferer by the observation's `source` field, so the source name and inferer name must match.  An inferer is a NO-OP if the observation has nothing useful to contribute (returns None), or raises a `ValueError` if the observation is malformed in a way the orchestrator should log and skip.", "id": "estorides_core/relationship_inference.py", "kind": "module", "label": "relationship_inference.py", "language": "py", "sha256": "8d040d960419e310", "symbol_count": 16, "symbols": [{"doc": "Translate a single observation into one or more knowledge-graph edges.\n\nArgs:\n    observation: the structured observation dict produced by the\n        orchestrator. Has keys: source, category, parsed, raw, meta.\n    query: the user's original query string. Useful for edges that\n        connect a finding back to the pivot (e.g. \"domain X\n        resolved to IP Y for query X\").\n    kg: the KnowledgeGraph to mutate. The inferer adds nodes and\n        edges via the existing `add_relationship()` API.\n\nReturns:\n    None. Side effects only: writes to the knowledge graph.", "kind": "class", "line": 36, "name": "RelationshipInferer", "signature": "class RelationshipInferer(Protocol)"}, {"doc": "Decorator: register `func` as the inferer for `source_name`.\n\nRe-registration is a debug log + overwrite; the orchestrator picks\nthe LAST registered inferer for a given source name, so a test\ncan monkey-patch an inferer without monkey-patching the source.", "kind": "method", "line": 63, "name": "register_inferer", "signature": "def register_inferer(source_name)"}, {"doc": "Dispatch an observation to its inferer (if any).\n\nReturns True if an inferer ran, False otherwise. An inferer that\nraises is logged at WARNING and returns False; the orchestrator\nkeeps going so one bad source doesn't poison the whole run.", "kind": "method", "line": 78, "name": "infer_relationship", "signature": "def infer_relationship(observation, query, kg)"}, {"kind": "method", "line": 105, "name": "_infer_dns", "signature": "def _infer_dns(observation, query, kg)"}, {"kind": "method", "line": 114, "name": "_infer_crtsh", "signature": "def _infer_crtsh(observation, query, kg)"}, {"kind": "method", "line": 122, "name": "_infer_shodan", "signature": "def _infer_shodan(observation, query, kg)"}, {"kind": "method", "line": 134, "name": "_infer_greynoise", "signature": "def _infer_greynoise(observation, query, kg)"}, {"kind": "method", "line": 143, "name": "_infer_abuseipdb", "signature": "def _infer_abuseipdb(observation, query, kg)"}, {"kind": "method", "line": 152, "name": "_infer_whois", "signature": "def _infer_whois(observation, query, kg)"}, {"kind": "method", "line": 163, "name": "_infer_urlscan", "signature": "def _infer_urlscan(observation, query, kg)"}, {"kind": "method", "line": 175, "name": "_infer_phonebook", "signature": "def _infer_phonebook(observation, query, kg)"}, {"kind": "method", "line": 186, "name": "_infer_ipapi", "signature": "def _infer_ipapi(observation, query, kg)"}, {"kind": "method", "line": 195, "name": "_infer_otx", "signature": "def _infer_otx(observation, query, kg)"}, {"kind": "method", "line": 208, "name": "_infer_nvd", "signature": "def _infer_nvd(observation, query, kg)"}, {"kind": "method", "line": 51, "name": "__call__", "signature": "def __call__(self, observation, query, kg)"}, {"kind": "method", "line": 70, "name": "deco", "signature": "def deco(func)"}]}, {"doc": "estorides_core.reliability_scoring ================================== Single source of truth for the ``confidence`` of an observation.  Replaces the two incompatible heuristics that used to live in :mod:`estorides_core.entity_extraction` (``min(1.0, c + 0.1)``) and :mod:`estorides_core.fusion_store` (``MAX(existing, incoming)``). The model is NATO Admiralty System (source reliability A-F x information credibility 1-6) extended with three operational factors: source type hierarchy (primary/secondary/tertiary), corroboration (``min(1, log10(1 + n))`` over independent sources), and freshness (exp half-life). Pure functions, no I/O, no logging, fail-loud on programmer input, fail-soft on operator input.  The public surface:  SourceReliability     enum str  {A, B, C, D, E, F} Credibility           enum int  {1..6} SourceType            enum str  {primary, secondary, tertiary} ConfidenceInput       dataclass, validated ConfidenceResult      dataclass frozen, JSON-serialisable compute_confidence    pure, O(1), no I/O merge_confidence      pure, O(1), no I/O reliability_from_name pure, never raises, never logs input source_type_from_name pure, never raises, never logs input", "id": "estorides_core/reliability_scoring.py", "kind": "module", "label": "reliability_scoring.py", "language": "py", "sha256": "8164103b81fe3014", "symbol_count": 16, "symbols": [{"doc": "NATO Admiralty source-reliability rating (A-F).", "kind": "class", "line": 37, "name": "SourceReliability", "signature": "class SourceReliability(str, Enum)"}, {"doc": "NATO Admiralty information-credibility rating (1-6).", "kind": "class", "line": 48, "name": "Credibility", "signature": "class Credibility(int, Enum)"}, {"doc": "Source type hierarchy: primary > secondary > tertiary.\n\nOrthogonal to NATO reliability: a primary source (official WHOIS) and a\nsecondary source (social media) can both be rated A, but the primary\ncontributes more weight because it is institutionally closer to the truth.", "kind": "class", "line": 59, "name": "SourceType", "signature": "class SourceType(str, Enum)"}, {"doc": "Validated input to :func:`compute_confidence`.", "kind": "class", "line": 243, "name": "ConfidenceInput", "signature": "class ConfidenceInput"}, {"doc": "Auditable output of :func:`compute_confidence` / :func:`merge_confidence`.", "kind": "class", "line": 263, "name": "ConfidenceResult", "signature": "class ConfidenceResult"}, {"doc": "``min(1, log10(1 + n))``.  0 sources → 0; 1 → 0.30; 9 → 1.0.", "kind": "method", "line": 280, "name": "_corroboration_weight", "signature": "def _corroboration_weight(n)"}, {"doc": "Exponential decay.  age=0 → 1.0; one half-life → 0.5.", "kind": "method", "line": 287, "name": "_freshness_weight", "signature": "def _freshness_weight(age_seconds, half_life_days)"}, {"kind": "method", "line": 297, "name": "_validate_score", "signature": "def _validate_score(value, field_name)"}, {"doc": "Clamp to the closed unit interval.", "kind": "method", "line": 302, "name": "_clamp01", "signature": "def _clamp01(value)"}, {"doc": "Compute the audit-trailed confidence score for one observation.\n\nPure: no I/O, no logging, no clock. Identical input identical output\nbit-by-bit. Bounded to [0, 1]. The formula is:\n\n    score = base * reliability_weight * credibility_weight\n          * source_type_weight * corroboration_weight * freshness_weight", "kind": "method", "line": 312, "name": "compute_confidence", "signature": "def compute_confidence(inp)"}, {"doc": "Merge a new observation's confidence into an existing entity score.\n\nThe result is ``max(existing, new_score)`` clamped to ``[0, 1]``. An\nunreliable new observation cannot raise a strongly-corroborated\nexisting one; a reliable new observation can lift a weakly-attested\none. The :class:`ConfidenceResult` exposes the weights of the *new*\nobservation so the audit trail is intact.", "kind": "method", "line": 350, "name": "merge_confidence", "signature": "def merge_confidence(existing, new_observation)"}, {"doc": "Look up the reliability for a source by name; never raises.\n\nOperator input is potentially adversarial. We do not log, do not\nvalidate beyond ``lower().strip()``, and never raise. Unknown names\nfall back to :data:`DEFAULT_RELIABILITY`.", "kind": "method", "line": 400, "name": "reliability_from_name", "signature": "def reliability_from_name(source_name)"}, {"doc": "Look up the source type hierarchy for a source by name; never raises.\n\nOperator input is potentially adversarial. Same contract as\n:func:`reliability_from_name`: no logging, no raising, no ReDoS.\nUnknown names fall back to :data:`DEFAULT_SOURCE_TYPE` (TERTIARY).", "kind": "method", "line": 415, "name": "source_type_from_name", "signature": "def source_type_from_name(source_name)"}, {"doc": "Numeric reliability weight for a source name; never raises.\n\n`overrides` maps a source name to an explicit reliability letter\n(used by the recon-fusion config). An invalid override letter falls\nback to C, matching the historical behaviour. Unknown names fall back\nto :data:`RELIABILITY_WEIGHT` for :data:`DEFAULT_RELIABILITY`.", "kind": "method", "line": 430, "name": "reliability_weight", "signature": "def reliability_weight(source_name, overrides)"}, {"doc": "Numeric weight for a reliability letter (A-F); never raises.", "kind": "method", "line": 452, "name": "reliability_weight_for_letter", "signature": "def reliability_weight_for_letter(letter)"}, {"kind": "method", "line": 253, "name": "__post_init__", "signature": "def __post_init__(self)"}]}, {"doc": "estorides_core.scope ===================== Bug-bounty scope classification.  Takes the assets a passive run surfaces (hosts, IPs, CIDRs) and decides, for each one, whether it is in-scope, out-of-scope, or unknown against a program's rules. Out-of-scope always wins so the operator never points active tooling at an excluded asset — the fastest way to get removed from a program.  Rule grammar (one rule per line, blank lines and `#` comments ignored):  *.example.com        wildcard host suffix (matches sub.example.com and example.com) example.com          exact host 192.0.2.0/24         CIDR network (IPv4 or IPv6) 198.51.100.7         single IP address re:^api[0-9]+\\\\.ex   regex (prefix ``re:``), matched against the raw asset  A single rules file may carry both lists, divided by a line whose content is ``## out-of-scope`` (case-insensitive); rules above the divider are in-scope, rules below are out-of-scope. The two lists can also be supplied independently when building a :class:`ScopeMatcher` directly.  The design is deliberately decoupled (SOLID): every grammar form is a small :class:`ScopeRule` with one ``matches`` method, rules are produced by an ordered list of detector factories (open/closed — add a form by appending one factory), and :class:`ScopeMatcher` only knows the out-beats-in precedence. No call site hard-codes a grammar.", "id": "estorides_core/scope.py", "kind": "module", "label": "scope.py", "language": "py", "sha256": "e27ab8f06fddee0d", "symbol_count": 39, "symbols": [{"doc": "Reduce a raw asset string to a comparable host or IP literal.\n\nStrips an optional scheme, any path/query, a port, surrounding\nwhitespace and a trailing dot, and lowercases the result. A value that\nis already a bare host or IP passes through unchanged (bar casing).", "kind": "function", "line": 52, "name": "normalise_asset", "signature": "def normalise_asset(raw)"}, {"doc": "True when `asset` parses as a bare IPv4 or IPv6 address.", "kind": "function", "line": 79, "name": "is_ip", "signature": "def is_ip(asset)"}, {"doc": "A single scope predicate. Implementations match exactly one grammar.", "kind": "class", "line": 89, "name": "ScopeRule", "signature": "class ScopeRule(ABC)"}, {"doc": "`*.example.com` — the apex and any subdomain of it.", "kind": "class", "line": 102, "name": "WildcardRule", "signature": "class WildcardRule(ScopeRule)"}, {"doc": "A single fully-qualified host, matched verbatim.", "kind": "class", "line": 117, "name": "ExactHostRule", "signature": "class ExactHostRule(ScopeRule)"}, {"doc": "An IPv4/IPv6 network; matches any address inside it.", "kind": "class", "line": 130, "name": "CidrRule", "signature": "class CidrRule(ScopeRule)"}, {"doc": "A compiled regex matched against the raw normalised asset.", "kind": "class", "line": 148, "name": "RegexRule", "signature": "class RegexRule(ScopeRule)"}, {"kind": "method", "line": 161, "name": "_wildcard_factory", "signature": "def _wildcard_factory(text)"}, {"kind": "method", "line": 168, "name": "_regex_factory", "signature": "def _regex_factory(text)"}, {"kind": "method", "line": 179, "name": "_cidr_factory", "signature": "def _cidr_factory(text)"}, {"kind": "method", "line": 188, "name": "_ip_factory", "signature": "def _ip_factory(text)"}, {"kind": "method", "line": 195, "name": "_exact_host_factory", "signature": "def _exact_host_factory(text)"}, {"doc": "Parse one rule line into a ScopeRule, or None for blank/comment/invalid.", "kind": "method", "line": 211, "name": "parse_rule", "signature": "def parse_rule(line)"}, {"doc": "Parse many rule lines, skipping blanks, comments and invalid entries.", "kind": "method", "line": 223, "name": "parse_rules", "signature": "def parse_rules(lines)"}, {"doc": "Classifies assets against in-scope and out-of-scope rule sets.\n\nOut-of-scope is evaluated first and wins outright, so an asset that\nmatches both a broad in-scope wildcard and a narrow out-of-scope rule\nis reported as out-of-scope.", "kind": "class", "line": 234, "name": "ScopeMatcher", "signature": "class ScopeMatcher"}, {"doc": "Build a matcher from a rules file, honouring the out-of-scope divider.\n\nLines above a ``## out-of-scope`` divider (case-insensitive) are\nin-scope; lines below are out-of-scope. A file with no divider is all\nin-scope.", "kind": "method", "line": 285, "name": "load_rules_file", "signature": "def load_rules_file(path)"}, {"doc": "Read assets from a file: a discover surface JSON or a flat host list.\n\nA JSON document is mined for assets in `domains`, `entities` (values of\nhost/ip types) and any top-level list of strings. Anything else is read\nas one asset per line.", "kind": "method", "line": 303, "name": "load_assets", "signature": "def load_assets(path)"}, {"doc": "Extract candidate assets from a parsed discover/result JSON document.", "kind": "method", "line": 322, "name": "_assets_from_json", "signature": "def _assets_from_json(doc)"}, {"doc": "Classified assets plus the flat lists an operator pipes onward.", "kind": "class", "line": 344, "name": "ScopeReport", "signature": "class ScopeReport"}, {"doc": "Classify `assets` with `matcher` and return a :class:`ScopeReport`.", "kind": "method", "line": 371, "name": "build_report", "signature": "def build_report(matcher, assets)"}, {"doc": "Write newline-delimited flat lists for piping into active tooling.\n\nProduces `in_scope_hosts.txt`, `in_scope_ips.txt` and `unknown.txt`\nunder `out_dir`. Returns the map of label to written path.", "kind": "method", "line": 381, "name": "write_flat_lists", "signature": "def write_flat_lists(report, out_dir)"}, {"doc": "True when `asset` (already normalised) is covered by this rule.", "kind": "method", "line": 93, "name": "matches", "signature": "def matches(self, asset)"}, {"doc": "Human-readable form of the rule, for reports and audit.", "kind": "method", "line": 97, "name": "describe", "signature": "def describe(self)"}, {"kind": "method", "line": 107, "name": "matches", "signature": "def matches(self, asset)"}, {"kind": "method", "line": 112, "name": "describe", "signature": "def describe(self)"}, {"kind": "method", "line": 122, "name": "matches", "signature": "def matches(self, asset)"}, {"kind": "method", "line": 125, "name": "describe", "signature": "def describe(self)"}, {"kind": "method", "line": 135, "name": "matches", "signature": "def matches(self, asset)"}, {"kind": "method", "line": 143, "name": "describe", "signature": "def describe(self)"}, {"kind": "method", "line": 153, "name": "matches", "signature": "def matches(self, asset)"}, {"kind": "method", "line": 156, "name": "describe", "signature": "def describe(self)"}, {"kind": "method", "line": 242, "name": "__init__", "signature": "def __init__(self, in_scope, out_of_scope)"}, {"kind": "method", "line": 251, "name": "in_rules", "signature": "def in_rules(self)"}, {"kind": "method", "line": 255, "name": "out_rules", "signature": "def out_rules(self)"}, {"doc": "Return IN_SCOPE, OUT_OF_SCOPE or UNKNOWN for a single asset.", "kind": "method", "line": 258, "name": "classify", "signature": "def classify(self, raw_asset)"}, {"doc": "Bucket many assets, returning sorted, de-duplicated lists.\n\nKeys are IN_SCOPE, OUT_OF_SCOPE and UNKNOWN. De-duplication is on\nthe normalised form so `HTTPS://Example.com/` and `example.com`\ncollapse to one entry.", "kind": "method", "line": 269, "name": "partition", "signature": "def partition(self, assets)"}, {"doc": "In-scope hostnames (everything in-scope that is not an IP).", "kind": "method", "line": 352, "name": "hosts", "signature": "def hosts(self)"}, {"doc": "In-scope bare IP addresses.", "kind": "method", "line": 357, "name": "ips", "signature": "def ips(self)"}, {"kind": "method", "line": 361, "name": "to_dict", "signature": "def to_dict(self)"}]}, {"doc": "estorides.search_telemetry.  Single source of truth for the operator-facing telemetry surface: the search-in-progress model, the onboarding catalog (keyboard shortcuts and tips) and the brand/emoji integrity invariants.  The module is pure: no Flask import, no I/O, no mutable global state. The web layer injects ``SearchTelemetry.context()`` into the Jinja render so the server and the browser share one vocabulary; the test suite asserts the served chrome honours the invariants. Numbers, copy and the brand vocabulary all resolve from the frozen ``TelemetryConfig`` instance, never from scattered literals.", "id": "estorides_core/search_telemetry.py", "kind": "module", "label": "search_telemetry.py", "language": "py", "sha256": "f79c4dbeb7d91247", "symbol_count": 22, "symbols": [{"doc": "Base class for every error raised by this module.", "kind": "class", "line": 37, "name": "SearchTelemetryError", "signature": "class SearchTelemetryError(Exception)"}, {"doc": "Raised when a phase key is not part of the configured vocabulary.", "kind": "class", "line": 41, "name": "UnknownPhaseError", "signature": "class UnknownPhaseError(SearchTelemetryError, KeyError)"}, {"doc": "Raised when a :class:`TelemetryConfig` violates a construction rule.", "kind": "class", "line": 45, "name": "InvalidTelemetryConfigError", "signature": "class InvalidTelemetryConfigError(SearchTelemetryError, ValueError)"}, {"doc": "Return the third-party brand tokens found in ``text``.\n\nMatching is case-insensitive and word-boundary aware so an ordinary word\nthat merely contains a brand as a substring does not produce a false\npositive. The returned tuple holds the canonical lowercase brand names in\nfirst-seen order, de-duplicated.", "kind": "method", "line": 75, "name": "disallowed_brands_in", "signature": "def disallowed_brands_in(text)"}, {"doc": "Return the emoji glyphs found in ``text``, de-duplicated in order.\n\nEmoji are codepoints in the pictographic blocks (Miscellaneous Symbols,\nDingbats, regional indicators, the supplementary pictographic planes) and\nthe emoji variation selector. Geometric line-symbols outside those blocks\nare permitted iconography and are not flagged.", "kind": "method", "line": 89, "name": "emoji_in", "signature": "def emoji_in(text)"}, {"doc": "Return percent-encoded supplementary-plane emoji sequences in ``text``.\n\nCatches an emoji smuggled into a ``data:`` URI (for example an emoji\nfavicon) as the UTF-8 lead bytes ``%F0%9F`` followed by two continuation\nbytes. The returned tuple holds the matched sequences in first-seen order.", "kind": "method", "line": 103, "name": "percent_encoded_emoji_in", "signature": "def percent_encoded_emoji_in(text)"}, {"doc": "A single keyboard shortcut: the key chord and what it does.", "kind": "class", "line": 117, "name": "KeyboardShortcut", "signature": "class KeyboardShortcut"}, {"doc": "A single onboarding tip: a short title and a one-line body.", "kind": "class", "line": 125, "name": "SplashTip", "signature": "class SplashTip"}, {"doc": "A search lifecycle phase: a stable key, a human label and activity flag.\n\n``active`` is ``True`` while work is in flight (the UI shows a spinner) and\n``False`` for the resting states ``idle``, ``done`` and ``error``.", "kind": "class", "line": 133, "name": "SearchPhase", "signature": "class SearchPhase"}, {"doc": "Immutable, render-ready snapshot of search progress.\n\nCarries the clamped counters, the percentage, the human label and the ARIA\nattributes a screen reader needs to announce a live progress region.", "kind": "class", "line": 146, "name": "ProgressView", "signature": "class ProgressView"}, {"doc": "Raise :class:`InvalidTelemetryConfigError` if any text leaks brand/emoji.", "kind": "method", "line": 167, "name": "_assert_clean", "signature": "def _assert_clean(label)"}, {"doc": "Frozen catalog: brand, tagline, shortcuts, tips and phase vocabulary.\n\n``__post_init__`` enforces every invariant in spec/search_telemetry.md so an\ninvalid catalog can never reach the UI. All construction failures raise\n:class:`InvalidTelemetryConfigError`.", "kind": "class", "line": 177, "name": "TelemetryConfig", "signature": "class TelemetryConfig"}, {"doc": "Service over a :class:`TelemetryConfig`: progress math and catalog views.\n\nStateless and side-effect free; safe to instantiate or share per request.", "kind": "class", "line": 217, "name": "SearchTelemetry", "signature": "class SearchTelemetry"}, {"doc": "Build the canonical Estorides telemetry catalog.", "kind": "method", "line": 309, "name": "_default_config", "signature": "def _default_config()"}, {"kind": "method", "line": 191, "name": "__post_init__", "signature": "def __post_init__(self)"}, {"kind": "method", "line": 223, "name": "__init__", "signature": "def __init__(self, config)"}, {"doc": "Return the keyboard-shortcut catalog.", "kind": "method", "line": 229, "name": "shortcuts", "signature": "def shortcuts(self)"}, {"doc": "Return the onboarding tips catalog.", "kind": "method", "line": 233, "name": "tips", "signature": "def tips(self)"}, {"doc": "Return the search-phase vocabulary.", "kind": "method", "line": 237, "name": "phases", "signature": "def phases(self)"}, {"doc": "Return the phase for ``key`` or raise :class:`UnknownPhaseError`.", "kind": "method", "line": 241, "name": "phase", "signature": "def phase(self, key)"}, {"doc": "Compute a clamped, render-ready :class:`ProgressView`.\n\nOut-of-range counters are clamped, never rejected; only an unknown\n``phase_key`` raises (:class:`UnknownPhaseError`). When ``total`` is not\nyet known (``<= 0``) and the phase is active the view is indeterminate.", "kind": "method", "line": 249, "name": "progress", "signature": "def progress(self, completed, total, phase_key)"}, {"doc": "Return the JSON-serialisable catalog for template/JS injection.", "kind": "method", "line": 288, "name": "context", "signature": "def context(self)"}]}, {"doc": "estorides_core.socmint ====================== Social Media Intelligence (SOCMINT) module.  Provides: * PlatformInfo — metadata about a known social platform * ProfileMatch — a single platform profile match * SocialMediaProfile — aggregated cross-platform profile * SocialMediaInferer — correlates usernames across all known platforms  The inferer uses the source registry to determine which platforms are available, and the entity resolution layer to fuse profiles that share the same handle across different services.", "id": "estorides_core/socmint.py", "kind": "module", "label": "socmint.py", "language": "py", "sha256": "fc4ae4c9398ef41f", "symbol_count": 12, "symbols": [{"doc": "Metadata about a known social media platform for cross-referencing.", "kind": "class", "line": 32, "name": "PlatformInfo", "signature": "class PlatformInfo"}, {"doc": "A single platform profile match for a username.", "kind": "class", "line": 81, "name": "ProfileMatch", "signature": "class ProfileMatch"}, {"doc": "Aggregated social media profile for a username across all platforms.", "kind": "class", "line": 105, "name": "SocialMediaProfile", "signature": "class SocialMediaProfile"}, {"doc": "Extract social media profile URLs from a text blob.\n\nScans text for common social media URL patterns and returns a\ndict of platform -> username for every match found.", "kind": "method", "line": 164, "name": "_extract_profile_urls", "signature": "def _extract_profile_urls(text)"}, {"doc": "Compute cross-platform confidence based on evidence strength.\n\nMore platforms = higher confidence. A verified profile (Keybase/GitHub)\nadds significant weight. The result is bounded to [0.0, 1.0].", "kind": "method", "line": 185, "name": "_confidence_for_platform_matches", "signature": "def _confidence_for_platform_matches(platform_count, has_verified, has_keybase)"}, {"doc": "Correlates usernames across social media platforms.\n\nPure: no I/O, no external dependencies. Uses the platform registry\nto determine which platforms exist and which YAML sources back them.\n\nThe inferer provides:\n  1. resolve(username) — build a SocialMediaProfile for a username\n  2. discover_from_text(text) — extract cross-platform links from text", "kind": "class", "line": 203, "name": "SocialMediaInferer", "signature": "class SocialMediaInferer"}, {"kind": "method", "line": 92, "name": "to_dict", "signature": "def to_dict(self)"}, {"kind": "method", "line": 119, "name": "to_dict", "signature": "def to_dict(self)"}, {"kind": "method", "line": 214, "name": "__init__", "signature": "def __init__(self)"}, {"doc": "Build a SocialMediaProfile for a username across all platforms.\n\nArgs:\n    username: The handle to search for.\n    platforms: Optional subset of platforms to check. If None,\n              checks all known platforms.\n\nReturns:\n    A serialised SocialMediaProfile dict. When no platform could\n    find the username, returns ``{\"profiles\": [], \"no_matches\": True}``.\n\nThis is a *speculative* resolution — it returns profile URL\ntemplates for each platform. The actual hit/miss determination\nhappens when the YAML sources execute their HTTP calls.", "kind": "method", "line": 218, "name": "resolve", "signature": "def resolve(self, username, platforms)"}, {"doc": "Extract social media profiles from a text blob.\n\nScans text for URLs pointing to known social platforms and\nreturns a dict of platform -> username for every match.\n\nThis is useful for enriching observations — a GitHub profile\nthat mentions a Twitter handle creates a cross-platform link.", "kind": "method", "line": 311, "name": "discover_from_text", "signature": "def discover_from_text(self, text)"}, {"doc": "Return the full platform registry as a serialisable list.", "kind": "method", "line": 344, "name": "platform_list", "signature": "def platform_list(self)"}]}, {"doc": "estorides_core.source_health_monitoring ======================================= Per-source operational health tracking.  Computes health status (healthy/degrading/stale/unknown), composite health score, and a dashboard of hot vs degrading sources from raw fetch counters. Pure functions: no I/O, no logging, deterministic.  Public surface::  SourceHealthStatus  enum  {healthy, degrading, stale, unknown} SourceHealthInput   dataclass, validated SourceHealthResult  dataclass frozen, JSON-serialisable SourceHealthConfig  dataclass, validated HealthDashboard     dataclass frozen, JSON-serialisable compute_health      pure, O(1) build_dashboard     pure, O(n)", "id": "estorides_core/source_health_monitoring.py", "kind": "module", "label": "source_health_monitoring.py", "language": "py", "sha256": "90516c8a9e395b28", "symbol_count": 14, "symbols": [{"doc": "Operational health classification for an OSINT source.", "kind": "class", "line": 31, "name": "SourceHealthStatus", "signature": "class SourceHealthStatus(str, Enum)"}, {"doc": "Thresholds for source health classification.\n\nEvery field has a corresponding ``ESTORIDES_HEALTH_*`` environment\nvariable override at import time. The defaults are conservative:\nthree failures in ten = degrading; a week offline = stale.", "kind": "class", "line": 42, "name": "SourceHealthConfig", "signature": "class SourceHealthConfig"}, {"doc": "Raw per-source data for health computation.\n\nAll fields are required; the caller (orchestrator, fusion analytics)\nextracts these from the fusion store or its own tracking.", "kind": "class", "line": 98, "name": "SourceHealthInput", "signature": "class SourceHealthInput"}, {"doc": "Health assessment for a single source.", "kind": "class", "line": 134, "name": "SourceHealthResult", "signature": "class SourceHealthResult"}, {"doc": "Aggregate dashboard statistics.", "kind": "class", "line": 160, "name": "DashboardSummary", "signature": "class DashboardSummary"}, {"doc": "Grouped health view: hot sources, degrading sources, aggregate stats.", "kind": "class", "line": 173, "name": "HealthDashboard", "signature": "class HealthDashboard"}, {"kind": "method", "line": 202, "name": "_clamp01", "signature": "def _clamp01(value)"}, {"doc": "Classify a source's health status based on thresholds.", "kind": "method", "line": 210, "name": "_classify", "signature": "def _classify(success_rate, avg_latency_ms, freshness_hours, fetch_count, config)"}, {"doc": "Compute the health assessment for a single source.\n\nPure: no I/O, no logging, deterministic. The formula is::\n\n    success_weight = success_rate ^ 2\n    latency_weight = clamp(1 - avg_latency_s / degrading_latency_s, 0, 1)\n    freshness_weight = clamp(1 - freshness_hours / stale_hours, 0, 1)\n    health_score = 0.5 * success_weight + 0.3 * latency_weight + 0.2 * freshness_weight", "kind": "method", "line": 235, "name": "compute_health", "signature": "def compute_health(inp, config)"}, {"doc": "Build a health dashboard from per-source health inputs.\n\nGroups sources into hot (healthy), degrading (degrading + stale), and\nunknown lists. Computes aggregate summary statistics.", "kind": "method", "line": 295, "name": "build_dashboard", "signature": "def build_dashboard(records, config)"}, {"kind": "method", "line": 58, "name": "__post_init__", "signature": "def __post_init__(self)"}, {"kind": "method", "line": 112, "name": "__post_init__", "signature": "def __post_init__(self)"}, {"kind": "method", "line": 146, "name": "to_dict", "signature": "def to_dict(self)"}, {"kind": "method", "line": 184, "name": "to_dict", "signature": "def to_dict(self)"}]}, {"doc": "estorides_core.source_loader ============================ Loads all YAML sources, normalises the schema, and provides a registry the rest of the engine can iterate over.", "id": "estorides_core/source_loader.py", "kind": "module", "label": "source_loader.py", "language": "py", "sha256": "0930bde9fa453cc9", "symbol_count": 20, "symbols": [{"doc": "A source is a YAML-defined OSINT data provider.\n\nStored as a dict for JSON-serialisation convenience, but exposes\nattribute access for ergonomic call sites.", "kind": "class", "line": 22, "name": "Source", "signature": "class Source(dict)"}, {"doc": "Loads YAML sources from the sources/ directory and exposes them by name.", "kind": "class", "line": 38, "name": "SourceRegistry", "signature": "class SourceRegistry"}, {"kind": "method", "line": 28, "name": "__init__", "signature": "def __init__(self, data)"}, {"kind": "method", "line": 31, "name": "__getattr__", "signature": "def __getattr__(self, key)"}, {"kind": "method", "line": 41, "name": "__init__", "signature": "def __init__(self, sources_dir)"}, {"kind": "method", "line": 47, "name": "load", "signature": "def load(self)"}, {"kind": "method", "line": 71, "name": "_load_file", "signature": "def _load_file(self, path)"}, {"kind": "method", "line": 111, "name": "_normalise", "signature": "def _normalise(self, raw)"}, {"kind": "method", "line": 200, "name": "get", "signature": "def get(self, name)"}, {"kind": "method", "line": 203, "name": "all", "signature": "def all(self)"}, {"kind": "method", "line": 206, "name": "by_category", "signature": "def by_category(self, category)"}, {"kind": "method", "line": 209, "name": "categories", "signature": "def categories(self)"}, {"kind": "method", "line": 212, "name": "names", "signature": "def names(self)"}, {"doc": "Return sources matching the given predicates.\n\n`max_contact` keeps only sources whose contact class is at or below\nthe given ceiling (e.g. \"none\" for a passive-only run, \"broker\" to\nalso allow third-party probes). Sources with an unknown contact\nclass are treated as the most exposing and thus excluded by any\nceiling below `active`.", "kind": "method", "line": 215, "name": "filter", "signature": "def filter(self)"}, {"doc": "Derive a filesystem-safe directory name from a category label.\n\nE.g. ``\"06. Breach Intelligence\"`` → ``\"06_breach_intelligence\"``.", "kind": "method", "line": 237, "name": "_category_dir_name", "signature": "def _category_dir_name(self, category)"}, {"doc": "Derive the filesystem path for a source based on its name and category.", "kind": "method", "line": 253, "name": "_source_path", "signature": "def _source_path(self, name, category)"}, {"doc": "Locate a source file on disk by name, scanning all category dirs.", "kind": "method", "line": 259, "name": "_find_source_file", "signature": "def _find_source_file(self, name)"}, {"doc": "Write a source dict to the correct YAML file, overwriting if exists.\n\nThis is a pure file-write operation — no normalisation, no registry\nupdate. The caller is responsible for reloading the registry if needed.\nReturns the path written.", "kind": "method", "line": 272, "name": "write_source_file", "signature": "def write_source_file(self, data)"}, {"doc": "Delete a source file by name. Raises KeyError if not found on disk.", "kind": "method", "line": 342, "name": "delete_source_file", "signature": "def delete_source_file(self, name)"}, {"doc": "Compact summary used by /api/status.", "kind": "method", "line": 351, "name": "summary", "signature": "def summary(self)"}]}, {"doc": "estorides_core.sqlite_store =========================== Shared plumbing for the hand-rolled SQLite stores.  Four stores (`monitoring.WatchStore`, `cases.CaseStore`, `entity_store.EntityStore`, `fusion_store.FusionStore`) used to carry byte-identical copies of:  * `__init__` — mkdir + lock + `sqlite3.connect(check_same_thread=False, isolation_level=None)` + WAL/foreign-keys pragmas + schema init; * `_init_schema` — execute every DDL statement under the lock; * `_tx` — `BEGIN` / `COMMIT` / `ROLLBACK` inside the lock; * `close` — swallow-and-close.  This module is the single source of truth for that boilerplate. A store subclasses :class:`SqliteStore`, declares `_DDL` and `_DEFAULT_PATH`, and overrides only its own queries.  Also exports :class:`DictMixin` for the 29 dataclasses whose `to_dict` was literally `asdict(self)`.", "id": "estorides_core/sqlite_store.py", "kind": "module", "label": "sqlite_store.py", "language": "py", "sha256": "7e364169fb6acdbe", "symbol_count": 8, "symbols": [{"doc": "Thread-safe SQLite base: one serialised connection behind a lock.\n\nContract:\n  * ``_DDL``          — list/tuple of schema statements, executed once.\n  * ``_DEFAULT_PATH`` — path used when the constructor gets ``None``.\n  * ``_PRAGMAS``      — connection pragmas applied before schema init.", "kind": "class", "line": 34, "name": "SqliteStore", "signature": "class SqliteStore"}, {"doc": "`to_dict()` for dataclasses that map 1:1 to a JSON object.", "kind": "class", "line": 109, "name": "DictMixin", "signature": "class DictMixin"}, {"kind": "method", "line": 50, "name": "__init__", "signature": "def __init__(self, path)"}, {"kind": "method", "line": 66, "name": "_init_schema", "signature": "def _init_schema(self)"}, {"kind": "method", "line": 72, "name": "_tx", "signature": "def _tx(self)"}, {"kind": "method", "line": 82, "name": "close", "signature": "def close(self)"}, {"kind": "method", "line": 89, "name": "__del__", "signature": "def __del__(self)"}, {"kind": "method", "line": 112, "name": "to_dict", "signature": "def to_dict(self)"}]}, {"doc": "estorides_core.ssrf_guard ========================= SSRF / SSRF-rebound protection for outbound HTTP.  The OSINT engine speaks to user-specified hosts (a domain, a URL from a paste, a link from a Telegram channel). Without filtering, a malicious or careless query can redirect our client at:  * RFC1918 private space              (10/8, 172.16/12, 192.168/16) * Loopback                           (127/8, ::1) * Link-local / cloud metadata        (169.254/16 — AWS, GCP, Azure IMDS) * CGNAT / Tailscale                  (100.64/10) * Multicast / reserved               (224/4, 240/4, 0/8, 255.255.255.255) * Documentation / benchmark          (192.0.2/24, 198.51.100/24, 203.0.113/24) * IPv6 link-local                    (fe80::/10) * IPv6 unique-local                  (fc00::/7) * IPv4-mapped IPv6                   (::ffff:0:0/96)  We block at TWO layers:  1. Canonicalise the literal host the source YAML gave us. Reject dotted forms that land in a reserved range and any IPv6 in a reserved prefix. 2. For hostnames, resolve A + AAAA at fetch time and reject if *any* answer lands in a reserved range. This is the TOCTOU-resistant layer that catches a DNS rebinding attack where the YAML says `attacker.com` but the resolver returns `169.254.169.254`.  Pinning the IP at the socket layer (the third leg of true TOCTOU defence) is intentionally out of scope: aiohttp doesn't expose socket-level", "id": "estorides_core/ssrf_guard.py", "kind": "module", "label": "ssrf_guard.py", "language": "py", "sha256": "190840d1c42c54af", "symbol_count": 12, "symbols": [{"kind": "class", "line": 99, "name": "GuardResult", "signature": "class GuardResult"}, {"kind": "method", "line": 110, "name": "_is_blocked_v4", "signature": "def _is_blocked_v4(ip)"}, {"doc": "Match an IPv6 textual address against the prefix table.\n\nLower-cased, leading zeros collapsed, no scope-id parsing required.", "kind": "method", "line": 114, "name": "_is_blocked_v6", "signature": "def _is_blocked_v6(addr)"}, {"doc": "Lowercase, strip brackets from IPv6 literals, return None if empty.", "kind": "method", "line": 126, "name": "_normalise_host", "signature": "def _normalise_host(host)"}, {"doc": "If `host` is a literal IP in a blocked range, return a reason string.\nOtherwise return None.", "kind": "method", "line": 134, "name": "_is_host_in_blocked_literal", "signature": "def _is_host_in_blocked_literal(host)"}, {"doc": "Resolve `host` to its A + AAAA records. Empty on failure.", "kind": "method", "line": 155, "name": "_resolve", "signature": "def _resolve(host)"}, {"doc": "Return True if `host` matches any entry in the allowlist.\n\nAn entry like `osiris.example.com` matches the host itself and any\nsubdomain. An entry like `*` matches everything (escape hatch).", "kind": "method", "line": 172, "name": "_matches_allowlist", "signature": "def _matches_allowlist(host, allowlist)"}, {"kind": "method", "line": 189, "name": "_load_allowlist", "signature": "def _load_allowlist()"}, {"doc": "Validate a URL for outbound fetch.\n\nArgs:\n    url: the URL string the source YAML wants us to hit.\n    resolve: when True (default) also resolve hostnames and reject\n        if any answer lands in a reserved range. Disable only in\n        tests with mocked DNS.\n\nReturns:\n    GuardResult with allowed/reason. Use as a bool.", "kind": "method", "line": 194, "name": "check_url", "signature": "def check_url(url)"}, {"doc": "Raise SSRFError if `url` is not safe to fetch.", "kind": "method", "line": 262, "name": "assert_safe", "signature": "def assert_safe(url)"}, {"doc": "Raised when an outbound URL fails the SSRF guard.", "kind": "class", "line": 269, "name": "SSRFError", "signature": "class SSRFError(ValueError)"}, {"kind": "method", "line": 105, "name": "__bool__", "signature": "def __bool__(self)"}]}, {"id": "estorides_core/supply_chain.py", "kind": "module", "label": "supply_chain.py", "language": "py", "sha256": "235ac06ffc385c89", "symbol_count": 13, "symbols": [{"kind": "class", "line": 54, "name": "ThirdParty", "signature": "class ThirdParty"}, {"kind": "class", "line": 66, "name": "SharedInfra", "signature": "class SharedInfra"}, {"kind": "class", "line": 78, "name": "Relationship", "signature": "class Relationship"}, {"kind": "class", "line": 89, "name": "SupplyChainResult", "signature": "class SupplyChainResult"}, {"kind": "method", "line": 106, "name": "detect_mx_provider", "signature": "def detect_mx_provider(mx_records)"}, {"kind": "method", "line": 114, "name": "detect_ns_provider", "signature": "def detect_ns_provider(ns_records)"}, {"kind": "method", "line": 122, "name": "detect_cdn", "signature": "def detect_cdn(cname)"}, {"kind": "method", "line": 129, "name": "analyse_third_parties", "signature": "def analyse_third_parties(third_parties, subsidiaries)"}, {"kind": "method", "line": 140, "name": "detect_shared_infrastructure", "signature": "def detect_shared_infrastructure(asn)"}, {"kind": "method", "line": 61, "name": "to_dict", "signature": "def to_dict(self)"}, {"kind": "method", "line": 73, "name": "to_dict", "signature": "def to_dict(self)"}, {"kind": "method", "line": 84, "name": "to_dict", "signature": "def to_dict(self)"}, {"kind": "method", "line": 96, "name": "to_dict", "signature": "def to_dict(self)"}]}, {"doc": "estorides_core.system_app_sources ================================= Kali OSINT CLI tools as first-class sources (`kind: system_app`).  A system_app source is a YAML-declared local binary executed through the existing :mod:`estorides_core.tool_runner` sandbox (allowlist, no shell, injection blocking, timeouts, output caps) whose output is parsed and fed into the **same** aggregation pipeline as HTTP sources: observations → entity extraction → fusion → reliability scoring → recon tiers.  The YAML schema differs from HTTP sources (`tool.binary` + `tool.args` instead of `tool.url` + `tool.params`) — one registry, two origin kinds, zero special-casing downstream.  Public surface:  KIND_SYSTEM_APP        \"system_app\" origin kind constant SystemAppResult        frozen dataclass, JSON-serialisable is_system_app          kind/binary predicate tool_available         shutil.which wrapper (monkeypatchable) render_args            {query}/{outdir} template substitution, pure parse_tool_output      parser dispatch with never-raise contract execute                full run: sandbox + parse + audit metadata TOOL_PARSER_NAMES      registered tool-output parsers (for fuzzing)", "id": "estorides_core/system_app_sources.py", "kind": "module", "label": "system_app_sources.py", "language": "py", "sha256": "a5786409ea3cf2a6", "symbol_count": 32, "symbols": [{"doc": "Normalised outcome of one system_app tool invocation.\n\nMirrors the contract in ``spec/system_app_sources.md``: failures are\nvalues, never exceptions.", "kind": "class", "line": 57, "name": "SystemAppResult", "signature": "class SystemAppResult"}, {"doc": "True when a source dict is a system_app (or has a binary tool).", "kind": "method", "line": 81, "name": "is_system_app", "signature": "def is_system_app(source)"}, {"doc": "Resolve a binary on the filesystem (shutil.which).", "kind": "method", "line": 91, "name": "tool_available", "signature": "def tool_available(binary)"}, {"doc": "Substitute ``{query}``/``{outdir}`` placeholders in an args template.\n\nUnknown placeholders are left verbatim (same contract as the HTTP\n``_safe_format``). Non-string template elements are programmer error\nand raise ``ValueError`` — a silently stringified int is a typo that\nmust be caught at source-authoring time, not at exec time.", "kind": "method", "line": 96, "name": "render_args", "signature": "def render_args(args, query, outdir)"}, {"doc": "Read a file, capped at ``cap`` bytes, UTF-8 with replacement.", "kind": "method", "line": 121, "name": "_read_capped", "signature": "def _read_capped(path, cap)"}, {"doc": "Best-effort JSON parse of tool output (tolerates log-prefix noise).", "kind": "method", "line": 131, "name": "_loads_lenient", "signature": "def _loads_lenient(text)"}, {"doc": "Factory for noise-reducing line parsers (one definition, N tools).\n\nKeep-lines must match *every* supplied condition; ``strip_prefixes``\nare removed from surviving lines so the operator sees data, not noise.", "kind": "method", "line": 150, "name": "_line_filter_parser", "signature": "def _line_filter_parser()"}, {"doc": "amass ``-json`` output: one JSON object per line (DNS + infra).", "kind": "method", "line": 193, "name": "parse_amass_json", "signature": "def parse_amass_json(payload)"}, {"doc": "maigret ``--json simple``: one object keyed by site name.", "kind": "method", "line": 225, "name": "parse_maigret_json", "signature": "def parse_maigret_json(payload)"}, {"doc": "phoneinfoga v2 ``scan``: a single JSON object (possibly after logs).", "kind": "method", "line": 247, "name": "parse_phoneinfoga_json", "signature": "def parse_phoneinfoga_json(payload)"}, {"kind": "method", "line": 260, "name": "parse_sherlock_text", "signature": "def parse_sherlock_text(payload)"}, {"kind": "method", "line": 265, "name": "parse_holehe_text", "signature": "def parse_holehe_text(payload)"}, {"kind": "method", "line": 270, "name": "parse_wafw00f_text", "signature": "def parse_wafw00f_text(payload)"}, {"kind": "method", "line": 275, "name": "parse_sublist3r_lines", "signature": "def parse_sublist3r_lines(payload)"}, {"kind": "method", "line": 280, "name": "parse_dnsrecon_text", "signature": "def parse_dnsrecon_text(payload)"}, {"kind": "method", "line": 285, "name": "parse_dnsenum_text", "signature": "def parse_dnsenum_text(payload)"}, {"kind": "method", "line": 292, "name": "parse_fierce_text", "signature": "def parse_fierce_text(payload)"}, {"kind": "method", "line": 297, "name": "parse_dmitry_text", "signature": "def parse_dmitry_text(payload)"}, {"kind": "method", "line": 302, "name": "parse_urlcrazy_text", "signature": "def parse_urlcrazy_text(payload)"}, {"kind": "method", "line": 307, "name": "parse_metagoofil_text", "signature": "def parse_metagoofil_text(payload)"}, {"kind": "method", "line": 312, "name": "parse_whatweb_text", "signature": "def parse_whatweb_text(payload)"}, {"kind": "method", "line": 317, "name": "parse_theharvester_text", "signature": "def parse_theharvester_text(payload)"}, {"kind": "method", "line": 322, "name": "parse_usufy_text", "signature": "def parse_usufy_text(payload)"}, {"kind": "method", "line": 327, "name": "parse_mailfy_text", "signature": "def parse_mailfy_text(payload)"}, {"kind": "method", "line": 332, "name": "parse_phonefy_text", "signature": "def parse_phonefy_text(payload)"}, {"kind": "method", "line": 337, "name": "parse_searchfy_text", "signature": "def parse_searchfy_text(payload)"}, {"doc": "Parse tool output with the declared parser; never raises.\n\nA parser that returns a non-list is wrapped; an empty/None parse falls\nback to the generic entity scanner over the raw text so a tool with a\nbroken/unknown parser still yields entities.", "kind": "method", "line": 365, "name": "parse_tool_output", "signature": "def parse_tool_output(source_name, parser_name, data)"}, {"doc": "Execute one system_app source through the tool_runner sandbox.\n\nFailures (missing binary, not allowlisted, wrong platform, injection,\ntimeout, crash) are ``SystemAppResult`` values — never exceptions.\n``{outdir}`` renders into a fresh private TemporaryDirectory that is\nremoved after parsing.", "kind": "method", "line": 396, "name": "execute", "signature": "def execute(source, query)"}, {"kind": "method", "line": 76, "name": "to_dict", "signature": "def to_dict(self)"}, {"kind": "method", "line": 165, "name": "parser", "signature": "def parser(payload)"}, {"kind": "method", "line": 418, "name": "fail", "signature": "def fail(code, message)"}, {"kind": "method", "line": 109, "name": "repl", "signature": "def repl(m)"}]}, {"id": "estorides_core/tech_fingerprint.py", "kind": "module", "label": "tech_fingerprint.py", "language": "py", "sha256": "5a38d28f004844cf", "symbol_count": 6, "symbols": [{"kind": "class", "line": 89, "name": "Tech", "signature": "class Tech"}, {"kind": "class", "line": 102, "name": "TechFingerprintResult", "signature": "class TechFingerprintResult"}, {"kind": "method", "line": 115, "name": "fingerprint", "signature": "def fingerprint(headers, html, cookies, status)"}, {"kind": "method", "line": 97, "name": "to_dict", "signature": "def to_dict(self)"}, {"kind": "method", "line": 107, "name": "to_dict", "signature": "def to_dict(self)"}, {"kind": "method", "line": 129, "name": "_add", "signature": "def _add(name, category, version, source, confidence)"}]}, {"doc": "estorides_core.tool_install =========================== One-click installation for missing Kali/OSINT CLI tools, modelled on the LazyOwn lazyaddon mechanism (`lazyaddons/*.yaml`) and wired into the GUI's results section: when a source fails with ``TOOL_NOT_FOUND`` the UI offers an \"Install tool\" button that calls this module.  Each installable tool has a YAML recipe in ``tool_recipes/<name>.yaml`` with the same shape as a LazyOwn lazyaddon:  name: sherlock description: ... os: any apt: sherlock            # optional: Debian/Kali package name git:                     # optional: git + install_command fallback repo_url: https://github.com/sherlock-project/sherlock.git install_path: sherlock install_command: pip install -r requirements.txt  Install methods (tried in order when the binary is still missing):  * ``apt`` — ``run0 apt-get update`` + ``run0 apt-get install -y <pkg>``. * ``git`` — ``git clone <repo_url> <install_path>`` (as the operator, no elevation) then ``<install_command>`` inside the clone (elevated only when it performs a system-wide install, e.g. ``pip install``).  Privilege elevation ------------------- Estorides is a GUI app; user management should be graphical. The default", "id": "estorides_core/tool_install.py", "kind": "module", "label": "tool_install.py", "language": "py", "sha256": "2a7ba1e634fea18a", "symbol_count": 27, "symbols": [{"doc": "One tool's install recipe, loaded from ``tool_recipes/<name>.yaml``.\n\nMirrors the LazyOwn lazyaddon ``tool`` block. ``apt`` is tried first;\n``git`` is the fallback. At least one of the two must be present.", "kind": "class", "line": 81, "name": "InstallRecipe", "signature": "class InstallRecipe"}, {"doc": "Outcome of one ``install_tool`` invocation. Failures are values.", "kind": "class", "line": 103, "name": "InstallResult", "signature": "class InstallResult"}, {"doc": "Prepend an elevation wrapper when required.\n\nPriority: ``run0`` (graphical polkit prompt — the GUI-friendly default),\nthen ``sudo``, then nothing when already root.", "kind": "method", "line": 117, "name": "_elevate", "signature": "def _elevate(cmd)"}, {"doc": "Run a subprocess as an argument list, capping output.\n\nReturns (exit_code, stdout, stderr). Never uses a shell: install\ncommands are shlex-split by the caller and run argument-by-argument,\nso a mangled recipe cannot inject a subcommand.", "kind": "method", "line": 132, "name": "_run", "signature": "def _run(cmd)"}, {"doc": "Reject hostile tokens in a trusted recipe's install_command.", "kind": "method", "line": 148, "name": "_check_shell_command", "signature": "def _check_shell_command(command)"}, {"doc": "True when an install_command writes system-wide and needs root.", "kind": "method", "line": 154, "name": "_needs_elevation", "signature": "def _needs_elevation(command)"}, {"doc": "True when ``name`` is a safe recipe/tool identifier (no separators).", "kind": "method", "line": 169, "name": "is_valid_recipe_name", "signature": "def is_valid_recipe_name(name)"}, {"doc": "True when ``name`` is a plausible bare binary name (no path).", "kind": "method", "line": 174, "name": "is_valid_binary", "signature": "def is_valid_binary(name)"}, {"doc": "Map recipe stem to its file from the directory listing.\n\nThe returned paths come from filesystem enumeration, never from\ncaller input: looking up a hostile name yields ``None`` instead of\na path built out of the name itself (CodeQL #48/#49/#52).", "kind": "method", "line": 179, "name": "_recipe_table", "signature": "def _recipe_table()"}, {"kind": "method", "line": 193, "name": "_recipe_path", "signature": "def _recipe_path(name)"}, {"doc": "Load a tool recipe from ``tool_recipes/<name>.yaml`` (or ``None``).\n\nA malformed recipe is logged and treated as absent so one bad file can\nnever break the whole registry. An invalid (traversal) name is likewise\ntreated as absent so a hostile ``<name>`` URL segment can never cause a\nfilesystem read outside ``TOOL_RECIPES_DIR``. Names outside the shipped\nrecipe allowlist are treated as absent before any path is built\n(CodeQL #48/#49/#52): only directory-listed stems ever reach the\nfilesystem.", "kind": "method", "line": 210, "name": "load_recipe", "signature": "def load_recipe(name)"}, {"doc": "True when a recipe exists for ``name`` (the UI gates the button on this).", "kind": "method", "line": 254, "name": "recipe_available", "signature": "def recipe_available(name)"}, {"doc": "True when the binary resolves on PATH (mirrors system_app_sources).", "kind": "method", "line": 259, "name": "tool_available", "signature": "def tool_available(binary)"}, {"doc": "Names of all tool recipe files in ``tool_recipes/`` (sorted).", "kind": "method", "line": 270, "name": "list_recipes", "signature": "def list_recipes()"}, {"doc": "One binary's install/readiness state for the doctor report.", "kind": "class", "line": 278, "name": "ToolStatus", "signature": "class ToolStatus"}, {"doc": "Map binary -> source names from the system_app YAML catalog.", "kind": "method", "line": 297, "name": "_system_app_binaries", "signature": "def _system_app_binaries(sources_dir)"}, {"doc": "Read-only readiness report over system_app binaries + recipes.\n\nNever installs, never elevates, never raises on malformed YAML:\nevery failure mode degrades to an entry, not an exception.", "kind": "method", "line": 321, "name": "doctor", "signature": "def doctor(sources_dir)"}, {"doc": "Install an apt package via the elevation wrapper (run0/sudo).", "kind": "method", "line": 358, "name": "_install_apt", "signature": "def _install_apt(recipe)"}, {"kind": "method", "line": 371, "name": "_tools_root", "signature": "def _tools_root()"}, {"doc": "Clone the repo (as operator) and run install_command (elevated if system-wide).", "kind": "method", "line": 375, "name": "_install_git", "signature": "def _install_git(recipe)"}, {"doc": "Install a missing tool from its recipe (if any).\n\nIf ``binary`` is given (the executable name from ``meta.tool_binary``),\nthe install is skipped when it already resolves on PATH unless ``force``.", "kind": "method", "line": 414, "name": "install_tool", "signature": "def install_tool(tool_name)"}, {"doc": "Post-install check: can the binary now be resolved?", "kind": "method", "line": 506, "name": "_verify", "signature": "def _verify(binary)"}, {"doc": "Minimal CLI: ``python -m estorides_core.tool_install <tool> [--force]``.", "kind": "method", "line": 516, "name": "main", "signature": "def main(argv)"}, {"kind": "method", "line": 95, "name": "has_apt", "signature": "def has_apt(self)"}, {"kind": "method", "line": 98, "name": "has_git", "signature": "def has_git(self)"}, {"kind": "method", "line": 113, "name": "to_dict", "signature": "def to_dict(self)"}, {"kind": "method", "line": 287, "name": "to_dict", "signature": "def to_dict(self)"}]}, {"id": "estorides_core/tool_runner.py", "kind": "module", "label": "tool_runner.py", "language": "py", "sha256": "ffe2bc21c1352c9f", "symbol_count": 15, "symbols": [{"kind": "class", "line": 17, "name": "ToolError", "signature": "class ToolError(Exception)"}, {"kind": "class", "line": 21, "name": "ToolNotAllowedError", "signature": "class ToolNotAllowedError(ToolError)"}, {"kind": "class", "line": 25, "name": "ToolInjectionError", "signature": "class ToolInjectionError(ToolError)"}, {"kind": "class", "line": 29, "name": "ToolNotFoundError", "signature": "class ToolNotFoundError(ToolError)"}, {"kind": "class", "line": 33, "name": "ToolTimeoutError", "signature": "class ToolTimeoutError(ToolError)"}, {"kind": "class", "line": 38, "name": "ToolResult", "signature": "class ToolResult"}, {"kind": "class", "line": 86, "name": "ToolErrorResult", "signature": "class ToolErrorResult"}, {"kind": "method", "line": 97, "name": "_check_injection", "signature": "def _check_injection(args)"}, {"kind": "method", "line": 107, "name": "_resolve_binary", "signature": "def _resolve_binary(tool_name)"}, {"kind": "method", "line": 114, "name": "_check_allowlist", "signature": "def _check_allowlist(tool_name)"}, {"kind": "method", "line": 121, "name": "_parse_entities_generic", "signature": "def _parse_entities_generic(stdout, tool_name)"}, {"kind": "method", "line": 156, "name": "run_tool", "signature": "def run_tool(tool_name, args, target, timeout, max_output_bytes, cwd)"}, {"kind": "method", "line": 51, "name": "to_dict", "signature": "def to_dict(self)"}, {"kind": "method", "line": 59, "name": "from_failure", "signature": "def from_failure(cls, tool_name, error_code, error_message, duration_s, exit_code, stdout, stderr, parsed_entities)"}, {"kind": "method", "line": 93, "name": "to_dict", "signature": "def to_dict(self)"}]}, {"doc": "estorides_core.transforms ========================= Maltego-style transform registry. A *transform* takes one entity `(type, value)` and returns related `nodes`/`links` in the same shape as :mod:`estorides_core.intel_resolver`, so the web layer and the D3 graph can merge the result with the existing expansion pipeline.  Transforms are grouped into the four-stage intelligence pipeline the operator walks a selector through:  data -> information -> intelligence -> counter_intelligence  Most transforms are thin filters over a single cross-feed resolution (`resolver.resolve`), which already fans a selector out across Wikidata, IP-API, OFAC and VirusTotal relationships. A few wrap the keyless Osiris probes (BGP, breach leaks, GitHub) and adapt their raw payloads into the node/link shape.  Public surface::  from estorides_core.transforms import registry registry.for_type(\"ip\")                 # -> [{\"id\",\"label\",\"tier\",...}, ...] registry.run(\"ip_to_sanctions\", \"ip\", \"1.2.3.4\")  Transforms can also be added without code: drop a ``*.yaml`` file in ``transforms/`` (same ease as ``sources/``)::  id: my_pivot label: My pivot", "id": "estorides_core/transforms.py", "kind": "module", "label": "transforms.py", "language": "py", "sha256": "2cf97d25cf0bf745", "symbol_count": 24, "symbols": [{"doc": "One named transform applicable to a set of entity types.", "kind": "class", "line": 73, "name": "Transform", "signature": "class Transform"}, {"kind": "method", "line": 98, "name": "_empty", "signature": "def _empty(root_type, value)"}, {"doc": "Resolve `(ent_type, value)` and keep only links whose relation is\nin `relations` (or every link when `relations` is None). The root\nnode plus any node touched by a kept link is returned.", "kind": "method", "line": 102, "name": "_resolver_filtered", "signature": "def _resolver_filtered(ent_type, value, relations)"}, {"kind": "method", "line": 123, "name": "_filter_runner", "signature": "def _filter_runner(relations)"}, {"kind": "method", "line": 132, "name": "_norm", "signature": "def _norm(s)"}, {"kind": "method", "line": 137, "name": "_osiris", "signature": "def _osiris()"}, {"kind": "method", "line": 145, "name": "_run_bgp", "signature": "def _run_bgp(ent_type, value)"}, {"kind": "method", "line": 171, "name": "_run_leaks", "signature": "def _run_leaks(ent_type, value)"}, {"kind": "method", "line": 196, "name": "_run_github", "signature": "def _run_github(ent_type, value)"}, {"doc": "Holds every transform and dispatches by id.", "kind": "class", "line": 227, "name": "TransformRegistry", "signature": "class TransformRegistry"}, {"kind": "method", "line": 324, "name": "_str_list", "signature": "def _str_list(raw)"}, {"doc": "Fixed nodes/links with ``{query}`` substitution (zero I/O).", "kind": "method", "line": 334, "name": "_static_runner", "signature": "def _static_runner(nodes_tpl, links_tpl)"}, {"doc": "Validate one YAML mapping into a Transform (None = skip).", "kind": "method", "line": 364, "name": "_transform_from_yaml", "signature": "def _transform_from_yaml(raw, origin)"}, {"doc": "Yield ``(kind, payload)`` tuples for the SSE stream endpoint.\n\nKinds: ``node`` / ``link`` per item, then ``done`` with counts, or\n``error`` when the run fails. `runner` override exists for tests.", "kind": "method", "line": 408, "name": "iter_sse_events", "signature": "def iter_sse_events(transform_id, ent_type, value, runner)"}, {"kind": "method", "line": 443, "name": "_T", "signature": "def _T(id, label, tier, applies, runner, description, output_types, cost)"}, {"kind": "method", "line": 85, "name": "summary", "signature": "def summary(self)"}, {"kind": "method", "line": 124, "name": "run", "signature": "def run(ent_type, value)"}, {"kind": "method", "line": 230, "name": "__init__", "signature": "def __init__(self)"}, {"kind": "method", "line": 233, "name": "register", "signature": "def register(self, t)"}, {"kind": "method", "line": 236, "name": "for_type", "signature": "def for_type(self, ent_type)"}, {"kind": "method", "line": 248, "name": "run", "signature": "def run(self, transform_id, ent_type, value)"}, {"doc": "Register every transform declared in ``*.yaml`` under `directory`.\n\nSame ease as ``sources/``: one file per pivot, multi-doc / list\ndocs supported. Broken files are skipped fail-soft; duplicates\noverwrite with a warning. Returns the number of transforms loaded.", "kind": "method", "line": 282, "name": "load_yaml_dir", "signature": "def load_yaml_dir(self, directory)"}, {"kind": "method", "line": 337, "name": "run", "signature": "def run(ent_type, value)"}, {"kind": "method", "line": 340, "name": "sub", "signature": "def sub(s, depth)"}]}, {"doc": "estorides_core.transliteration ============================== Cross-script name normalisation for entity resolution.  State-level person/organisation matching lives or dies on names that arrive in different writing systems: a sanctions list spells a target in Cyrillic, a leak spells him in Latin, a news article in Arabic. This module folds all of them onto a single comparable Latin (ASCII) skeleton so the resolver can score them against each other.  It is deliberately stdlib-only (no `unidecode`, no `transliterate`) so it adds zero hard dependencies, consistent with the rest of the engine. The maps are pragmatic romanisations (BGN/PCGN-leaning for Cyrillic/Greek, ALA-LC-leaning for Arabic), not bit-exact standards; for fuzzy name matching, recall matters more than scholarly fidelity.  Two representations are produced:  * ``to_latin(text)`` — best-effort Latin transliteration, lowercased and stripped of diacritics. ``\"Владимир\" -> \"vladimir\"``. * ``consonant_skeleton(text)`` — the Latin form with vowels removed, so a vowelless Arabic spelling can still meet a fully-vowelled Latin one. Arabic script omits short vowels, so ``\"محمد\"`` romanises to ``\"mhmd\"`` while the Latin source has ``\"muhammad\"``; comparing consonant skeletons (``\"mhmd\"`` vs ``\"mhmd\"``) recovers the match.", "id": "estorides_core/transliteration.py", "kind": "module", "label": "transliteration.py", "language": "py", "sha256": "8a6f960604e64f81", "symbol_count": 4, "symbols": [{"doc": "Drop combining marks via NFKD decomposition.\n\nFolds ``é`` -> ``e``, ``ü`` -> ``u``, full-width forms to ASCII, and so\non. Characters that have no compatibility decomposition pass through\nunchanged and are handled by the per-script map instead.", "kind": "function", "line": 76, "name": "_strip_diacritics", "signature": "def _strip_diacritics(text)"}, {"doc": "Return a lowercased, diacritic-free Latin transliteration.\n\nThe pipeline is: casefold -> NFKD diacritic strip (so accented Greek\nand Latin fold to their base letters) -> per-character script map\n(Cyrillic/Greek/Arabic) -> keep only ``[a-z0-9 ]``. Casefolding and\nstripping run *before* the map so that uppercase and accented source\nletters reach the lowercase, accent-free keys the map is written for.\nWhitespace is collapsed to single spaces and the result is trimmed.\nNon-mappable characters (e.g. unmapped CJK) are dropped, which is the\nsafe failure mode for fuzzy matching.", "kind": "function", "line": 87, "name": "to_latin", "signature": "def to_latin(text)"}, {"doc": "Return the Latin transliteration with vowels and spaces removed.\n\nThis is the vowel-insensitive comparison key. Abjad scripts (Arabic,\nHebrew) routinely omit short vowels, so two spellings of the same name\ncan only be reconciled on their consonant skeletons. The first\ncharacter is preserved even if it is a vowel, because name-initial\nvowels are usually written and carry signal.\n\nAdjacent duplicate letters are collapsed so that gemination written as\na doubled Latin consonant (``Muhammad`` -> ``mhmmd``) reconciles with a\nscript that marks it with a diacritic instead of doubling the letter\n(Arabic ``محمد`` -> ``mhmd``).", "kind": "function", "line": 112, "name": "consonant_skeleton", "signature": "def consonant_skeleton(text)"}, {"doc": "True if any character is outside the Basic Latin / Latin-1 range.\n\nUsed by the resolver to decide whether the cross-script path is worth\ntaking for a given value before paying for transliteration of both\nsides of a comparison.", "kind": "function", "line": 139, "name": "is_non_latin", "signature": "def is_non_latin(text)"}]}, {"doc": "estorides_core.validation ========================= Input validation for free-form queries.  OSINT platforms ingest user-controlled text and turn it into network requests. A bare string is a free pass for: control characters, billion- laughing-doesn't-laugh payloads, ReDoS-inducing regex inputs, billion- byte blobs, and \"clever\" Unicode lookalikes that break downstream parsers. This module is the first line of defence: every public entry point (`api_run`, the CLI `run` subcommand) should pass the user query through `validate_query` before it touches a source.  Rules:  * Trim, collapse internal whitespace, strip control chars. * Cap length: 1-512 chars. Long enough for an indictment, short enough that no single run can become a DoS. * Reject ASCII control chars (except space, tab). * Reject Unicode bidi overrides (U+202A-202E, U+2066-2069) — these are the classic \"swap the file extension\" phishing trick and they have no business in an OSINT query. * Detect query type via the existing detector and require it to be one of the recognised shapes; reject anything that smells like a free-form text dump. * Return a typed `Query` object so callers don't have to redo the detection step.", "id": "estorides_core/validation.py", "kind": "module", "label": "validation.py", "language": "py", "sha256": "fec3cc682d05ef04", "symbol_count": 6, "symbols": [{"doc": "Raised when a query fails validation. The reason is in `.reason`.", "kind": "class", "line": 55, "name": "QueryValidationError", "signature": "class QueryValidationError(ValueError)"}, {"doc": "A validated, normalised query string.", "kind": "class", "line": 63, "name": "Query", "signature": "class Query"}, {"kind": "method", "line": 73, "name": "_strip_and_collapse", "signature": "def _strip_and_collapse(text)"}, {"doc": "Validate and normalise a user query string.\n\nArgs:\n    raw: the user-supplied query, exactly as received.\n    max_length: hard cap on the normalised length. Default 512.\n\nRaises:\n    QueryValidationError if the query is empty, oversized, contains\n    forbidden characters, or resolves to a type the engine cannot\n    dispatch on.\n\nReturns:\n    A `Query` with the normalised text and detected type.", "kind": "method", "line": 85, "name": "validate_query", "signature": "def validate_query(raw)"}, {"kind": "method", "line": 57, "name": "__init__", "signature": "def __init__(self, reason, message)"}, {"kind": "method", "line": 69, "name": "__str__", "signature": "def __str__(self)"}]}, {"id": "estorides_core/vuln_correlation.py", "kind": "module", "label": "vuln_correlation.py", "language": "py", "sha256": "ba36c55f0509cd31", "symbol_count": 11, "symbols": [{"kind": "class", "line": 14, "name": "DefaultCred", "signature": "class DefaultCred"}, {"kind": "class", "line": 24, "name": "VulnEntry", "signature": "class VulnEntry"}, {"kind": "class", "line": 43, "name": "VulnCorrelationResult", "signature": "class VulnCorrelationResult"}, {"kind": "method", "line": 141, "name": "_parsed_version", "signature": "def _parsed_version(version)"}, {"kind": "method", "line": 153, "name": "_version_in_range", "signature": "def _version_in_range(version, v_start, v_end)"}, {"kind": "method", "line": 169, "name": "lookup_cve_for_tech", "signature": "def lookup_cve_for_tech(tech_name, version)"}, {"kind": "method", "line": 202, "name": "correlate_technologies", "signature": "def correlate_technologies(technologies)"}, {"kind": "method", "line": 228, "name": "compute_attack_readiness", "signature": "def compute_attack_readiness(vulnerabilities)"}, {"kind": "method", "line": 19, "name": "to_dict", "signature": "def to_dict(self)"}, {"kind": "method", "line": 38, "name": "to_dict", "signature": "def to_dict(self)"}, {"kind": "method", "line": 52, "name": "to_dict", "signature": "def to_dict(self)"}]}, {"doc": "estorides_core.web_security ===========================  Production-grade web hardening helpers used by the Flask app factory.  Responsibilities ---------------- * Security headers on every response (CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, HSTS when behind TLS). * Optional CORS allowlist (the platform is a local single-user tool by default — CORS is opt-in, not opt-out, so the default is a tight same-origin policy). * A defensive request-size guard that rejects oversized JSON / form bodies before they reach a route handler (the rest of the app trusts `request.get_json()` blindly). * A Werkzeug-debugger kill-switch. Production deployments must never expose the interactive debugger console — this helper refuses to register routes when the dev debugger is on, and logs the attempt at WARNING level. * A bearer-token auth gate (`require_auth`) for sensitive endpoints. When the operator sets `ESTORIDES_AUTH_TOKEN`, the sensitive `/api/*` surface stops trusting anonymous callers; the legitimate UI gets the token from a `<meta name=\"estorides-auth-token\">` tag rendered into `index.html` and includes it as a header on every fetch.  When `ESTORIDES_AUTH_TOKEN` is unset, the system **auto-generates** a random 64-hex-char token on startup and prints it to the terminal. This ensures API abuse protection is always on — no login page needed, no explicit registration.  Why a dedicated module? -----------------------", "id": "estorides_core/web_security.py", "kind": "module", "label": "web_security.py", "language": "py", "sha256": "bfcb40d164e7ad4d", "symbol_count": 22, "symbols": [{"doc": "Build a safe HTTPS redirect target from a trusted host and client path.\n\nThe client controls ``path``/``query_string``, so they are treated as\nhostile (the standing doctrine: never trust user input). We parse the\nreconstructed URL and refuse to emit a Location header whose scheme is\nnot ``https`` or whose host differs from the configured ``public_host``.\nOn any mismatch we fall back to a bare host root, so a crafted ``//evil``\nor ``\\evil`` path can never become an open redirect.", "kind": "function", "line": 58, "name": "build_https_url", "signature": "def build_https_url(public_host, path, query_string)"}, {"doc": "Resolved security policy for the Flask app.\n\nAll fields are read from the environment at import time and frozen so\nthe policy cannot drift at runtime. Changing a knob requires a restart\n— the right call for a tool that mostly runs as a long-lived daemon.", "kind": "class", "line": 87, "name": "WebSecurityConfig", "signature": "class WebSecurityConfig"}, {"kind": "method", "line": 137, "name": "_env_str", "signature": "def _env_str(name, default)"}, {"doc": "Resolve the security policy from env vars.\n\nESTORIDES_CORS_ORIGINS    comma-separated list, e.g. \"https://app.example.com\"\nESTORIDES_MAX_BODY_BYTES  int, default 1 MiB\nESTORIDES_HSTS            1 to emit Strict-Transport-Security\nESTORIDES_FORCE_HTTPS     1 to redirect plain http to https (only meaningful behind TLS)\nESTORIDES_CSP             override the default Content-Security-Policy\nESTORIDES_PUBLIC_HOST     public hostname for HTTPS redirect (default localhost:5050)", "kind": "method", "line": 144, "name": "load_security_config", "signature": "def load_security_config()"}, {"doc": "Wire security middleware into a Flask app.\n\nIdempotent: calling twice is a no-op (we re-attach, but Flask keeps the\nlast hook, and our hooks are stateless). Returns the resolved config so\nthe caller can echo it in a startup banner.", "kind": "method", "line": 169, "name": "install_security", "signature": "def install_security(app, cfg)"}, {"doc": "Pull the bearer token from header, alt-header, cookie, or query param.\n\nHeader order matters: an explicit `Authorization: Bearer` always wins\nover a cookie (the cookie is the fallback for the browser UI; the\nheader is what scripts and curl will use).\n\nQuery-param ``?token=`` is a **last resort** for Server-Sent Events\n(``EventSource`` cannot set custom headers). It leaks into server\naccess logs — we accept this limitation because there is no other\ntransport for streaming endpoints in a browser. The token is never\naccepted from query params on POST/PUT/DELETE requests.", "kind": "method", "line": 286, "name": "_extract_bearer_token", "signature": "def _extract_bearer_token()"}, {"doc": "Build the auth gate from the current environment.\n\nWhen `ESTORIDES_AUTH_TOKEN` is set, the gate uses that value.\nWhen unset, the system auto-generates a random 64-hex-char token,\nstores it in `AUTO_GENERATED_TOKEN` (so the startup banner can\ndisplay it), and returns an enabled gate. This guarantees API\nabuse protection is always active — no login screen, no explicit\nenrolment.", "kind": "method", "line": 321, "name": "make_auth_gate", "signature": "def make_auth_gate()"}, {"doc": "Bearer-token gate applied to sensitive routes.\n\n`required_token` is the single shared secret. `None` disables the gate\n(local-trust mode). Comparison is constant-time.", "kind": "class", "line": 341, "name": "AuthGate", "signature": "class AuthGate"}, {"doc": "Decorator: enforce the bearer-token gate on a view.\n\nBehaviour:\n  * gate disabled (no env var) → pass-through, no overhead.\n  * gate enabled, token missing → 401 with `WWW-Authenticate: Bearer`.\n  * gate enabled, token present but wrong → 401 (same shape, constant-\n    time compare on the server side).\n\nUse on every endpoint that reads or mutates operator-private data:\ncases, run, run/stream/*, discover/*, export, intel/*, transform/*,\nosiris/*, graph, status.", "kind": "method", "line": 388, "name": "require_auth", "signature": "def require_auth(view)"}, {"doc": "Attach the gate to a Flask app and a module-level slot.\n\nTwo consumers read the gate: the `require_auth` decorator (module\nslot, so it works even when called outside a request context) and\n`auth_meta_for_index()` (so `index.html` can be rendered with the\ntoken embedded for the UI to pick up).", "kind": "method", "line": 421, "name": "install_auth_gate", "signature": "def install_auth_gate(app, gate)"}, {"kind": "method", "line": 440, "name": "_current_gate", "signature": "def _current_gate()"}, {"doc": "Return the auto-generated token (None if user set ESTORIDES_AUTH_TOKEN manually).", "kind": "method", "line": 444, "name": "auto_generated_token", "signature": "def auto_generated_token()"}, {"kind": "method", "line": 128, "name": "is_cors_enabled", "signature": "def is_cors_enabled(self)"}, {"doc": "CORS is opt-in; this is the runtime check used by the after_request hook.", "kind": "method", "line": 132, "name": "is_origin_allowed", "signature": "def is_origin_allowed(self)"}, {"kind": "method", "line": 217, "name": "_security_headers", "signature": "def _security_headers(resp)"}, {"kind": "method", "line": 250, "name": "_cors_preflight", "signature": "def _cors_preflight()"}, {"kind": "method", "line": 351, "name": "enabled", "signature": "def enabled(self)"}, {"kind": "method", "line": 354, "name": "check", "signature": "def check(self)"}, {"doc": "Token to embed in `index.html` so the UI can auto-authenticate.\n\nReturns `None` when the gate is off (the UI then omits the meta\ntag and every call goes through anonymously, which is the\nlocal-trust default).", "kind": "method", "line": 362, "name": "auth_meta_for_index", "signature": "def auth_meta_for_index(self)"}, {"doc": "Arguments for `set_cookie` to install the session cookie.\n\n`Secure` is set when the request itself is over HTTPS or the operator\nrequested ESTORIDES_FORCE_HTTPS=1 (in that case we know they're behind\nTLS). `SameSite=Lax` keeps the cookie from cross-site POSTs.", "kind": "method", "line": 371, "name": "issue_session_cookie_kwargs", "signature": "def issue_session_cookie_kwargs(self)"}, {"kind": "method", "line": 402, "name": "wrapper", "signature": "def wrapper()"}, {"kind": "method", "line": 203, "name": "_redirect_to_https", "signature": "def _redirect_to_https()"}]}, {"doc": "estorides_export", "id": "estorides_export/__init__.py", "kind": "module", "label": "__init__.py", "language": "py", "sha256": "44ef6470bd731a33", "symbol_count": 0, "symbols": []}, {"doc": "estorides_export.encryption =========================== Optional age-encrypted report export.  The default export path produces STIX / MISP / GraphML as plaintext JSON on disk, which is fine for a local single-user install. For deployments where the report itself is the deliverable (analyst hands a file to a customer, regulator, court), the file should be encrypted at rest with a recipient's public key.  This module wraps the optional `age` encryption tool (`age` is a modern, audited replacement for gpg; the binary is `age` from https://age-encryption.org). If `age` is not on PATH the export falls back to a plaintext file and logs a warning — never to a broken file. This is the \"graceful degradation\" posture: the operator decides whether encryption is required by whether the recipient public key is set.  Three entry points:  encrypt_file(plaintext_path, recipient_pubkey) -> ciphertext_path Encrypts a single file to a recipient's age public key. The resulting file is `<name>.age` next to the original. Returns the ciphertext path.  export_stix_encrypted(kg, recipient_pubkey, path) -> ciphertext_path export_misp_encrypted(kg, recipient_pubkey, path) -> ciphertext_path Composed: build the bundle, write to a tempfile, encrypt in place, return the .age path.", "id": "estorides_export/encryption.py", "kind": "module", "label": "encryption.py", "language": "py", "sha256": "56bc4bd5ec2b8f3e", "symbol_count": 4, "symbols": [{"kind": "function", "line": 47, "name": "_have_age", "signature": "def _have_age()"}, {"doc": "Encrypt `plaintext_path` to `<plaintext_path>.age` for the recipient.\n\nReturns the ciphertext path. Raises RuntimeError if `age` is\nmissing or the encryption subprocess fails — the orchestrator\ncatches and falls back to plaintext. Raises ValueError if\n`recipient_pubkey` doesn't look like an age public key.\n\nValidation order is: key shape (cheap, no exec) → binary\npresence (filesystem stat) → subprocess. A malformed key is\nalways a programmer error and surfaces as ValueError; a missing\nbinary is an environment problem and surfaces as RuntimeError.", "kind": "function", "line": 51, "name": "encrypt_file", "signature": "def encrypt_file(plaintext_path, recipient_pubkey)"}, {"doc": "Build the STIX bundle, write to disk, encrypt to <path>.age.\n\n`path` is the plaintext filename; the returned path is the\nencrypted artefact next to it. The plaintext is removed once\nencryption succeeds so reports/ never accumulates raw intel\nbundles — that was the disk-residue problem fixed for issue #8.", "kind": "function", "line": 100, "name": "export_stix_encrypted", "signature": "def export_stix_encrypted(kg, recipient_pubkey, path)"}, {"kind": "function", "line": 128, "name": "export_misp_encrypted", "signature": "def export_misp_encrypted(kg, recipient_pubkey, path)"}]}, {"doc": "estorides_export.misp ===================== Minimal MISP event JSON. Mirrors STIX in spirit but matches the MISP galaxy/cluster schema used by the MISP project.  This is *not* a complete MISP integration — it's enough to import into MISP as an event.", "id": "estorides_export/misp.py", "kind": "module", "label": "misp.py", "language": "py", "sha256": "1534f7a2e2df1427", "symbol_count": 3, "symbols": [{"kind": "function", "line": 36, "name": "event_from_graph", "signature": "def event_from_graph(kg)"}, {"kind": "function", "line": 65, "name": "_category", "signature": "def _category(ent_type)"}, {"kind": "function", "line": 79, "name": "export", "signature": "def export(kg, path)"}]}, {"id": "estorides_export/recon_report.py", "kind": "module", "label": "recon_report.py", "language": "py", "sha256": "99c3dfcae7431c0e", "symbol_count": 11, "symbols": [{"kind": "class", "line": 23, "name": "ReportMetadata", "signature": "class ReportMetadata"}, {"kind": "class", "line": 40, "name": "ReportSection", "signature": "class ReportSection"}, {"kind": "class", "line": 51, "name": "ReportResult", "signature": "class ReportResult"}, {"kind": "method", "line": 61, "name": "redact_sensitive", "signature": "def redact_sensitive(text)"}, {"kind": "method", "line": 67, "name": "build_subdomain_tree", "signature": "def build_subdomain_tree(subdomains)"}, {"kind": "method", "line": 92, "name": "build_executive_summary", "signature": "def build_executive_summary(critical_findings, total_targets, domain, classification)"}, {"kind": "method", "line": 115, "name": "generate_report", "signature": "def generate_report(query, target_scoring, metadata)"}, {"kind": "method", "line": 29, "name": "__post_init__", "signature": "def __post_init__(self)"}, {"kind": "method", "line": 35, "name": "to_dict", "signature": "def to_dict(self)"}, {"kind": "method", "line": 46, "name": "to_dict", "signature": "def to_dict(self)"}, {"kind": "method", "line": 57, "name": "to_dict", "signature": "def to_dict(self)"}]}, {"doc": "estorides_export.report =======================  Markdown report generation for finished cases.  Design choices -------------- * Pure function. The report builder takes a case dict + a list of entities + (optionally) a previous case id, and returns a Markdown string. No I/O happens here — the caller (CLI, web, scheduled job) decides where the report lands. This keeps the formatter unit- testable in isolation. * Progressive disclosure. The first 30 lines carry the TL;DR so a reviewer can decide in 10 seconds whether the case is worth a deep read. Full IOCs and source roster come after a `---` separator. * Diff support. When `previous_case_id` is supplied, the report includes a \"what's new since last run\" section that mirrors `CaseStore.diff_entities` — same keys, no double bookkeeping. * No LLM dependency. A separate LLM-narrative step can be wired on top, but the baseline report must work even when no LLM backend is configured (the engine returns a stub analysis in that case).", "id": "estorides_export/report.py", "kind": "module", "label": "report.py", "language": "py", "sha256": "4df6132ac382db27", "symbol_count": 6, "symbols": [{"doc": "Top-of-page executive summary. 6-10 lines max.", "kind": "function", "line": 37, "name": "_tldr", "signature": "def _tldr(case, entities, sources_queried, sources_succeeded, diff)"}, {"doc": "The sections the next responder (CTI team, SOC) actually pastes\ninto a ticket. Domains, IPs, emails, hashes, CVEs, crypto addresses.", "kind": "function", "line": 77, "name": "_iocs", "signature": "def _iocs(entities)"}, {"doc": "The \"what's new since last run\" block. Empty when no baseline.", "kind": "function", "line": 121, "name": "_diff_section", "signature": "def _diff_section(diff)"}, {"doc": "The LLM analysis (or stub) embedded verbatim in a code block.", "kind": "function", "line": 158, "name": "_analysis", "signature": "def _analysis(case)"}, {"kind": "function", "line": 179, "name": "_meta_footer", "signature": "def _meta_footer(case, sources_queried, sources_succeeded)"}, {"doc": "Build a Markdown report for `case`.\n\nParameters\n----------\ncase\n    The case row, as returned by `CaseStore.get_case`. `analysis_json`\n    is optional; if present it is embedded verbatim.\nentities\n    The full entity list. If omitted, only the counts in the case\n    row are used (TL;DR still works, IOC section is empty).\ndiff\n    Optional output of `CaseStore.diff_entities(a, b)`. When given,\n    a \"what's new\" section is appended before the analysis.\n\nReturns\n-------\nstr\n    The full Markdown document. UTF-8, LF newlines, no trailing\n    newline (callers add their own if writing to a file).", "kind": "function", "line": 196, "name": "render_markdown_report", "signature": "def render_markdown_report(case, entities, sources_queried, sources_succeeded, diff)"}]}, {"doc": "estorides_export.stix ===================== STIX 2.1 bundle export. Each unique entity becomes a STIX object (identity, ipv4-addr, domain-name, email-addr, software, vulnerability, url). Co-occurrences become sighting relationships. Sourced by the identity \"Estorides\".  Spec: https://docs.oasis-open.org/cti/stix/v2.1/cs01/stix-v2.1-cs01.html", "id": "estorides_export/stix.py", "kind": "module", "label": "stix.py", "language": "py", "sha256": "9be99084c674ab8e", "symbol_count": 4, "symbols": [{"kind": "function", "line": 28, "name": "_id", "signature": "def _id(stix_type)"}, {"kind": "function", "line": 32, "name": "_now", "signature": "def _now()"}, {"kind": "function", "line": 55, "name": "bundle_from_graph", "signature": "def bundle_from_graph(kg)"}, {"kind": "function", "line": 145, "name": "export", "signature": "def export(kg, path)"}]}, {"doc": "estorides_llm", "id": "estorides_llm/__init__.py", "kind": "module", "label": "__init__.py", "language": "py", "sha256": "7830a15c93b8e07b", "symbol_count": 0, "symbols": []}, {"doc": "estorides_llm.intelligence_prompts ================================== System prompt and context-formatting helpers for the LLM analyst.  The system prompt is the single most important piece of text in the whole stack. It is what turns \"a wall of OSINT JSON\" into \"a Palantir-grade intelligence brief\". The previous version was a six-line paragraph that read like a CV; this one is structured the way senior analysts actually brief:  * Persona is set first and never compromised * Output format is prescribed (BLUF, confidence-graded findings, recommended actions) * Hard constraints are explicit (no fabrication, citation required, \"I don't know\" is acceptable)  Three prompt flavours are exposed:  * SYSTEM_PROMPT — default, used by the orchestrator * BLUF_PROMPT   — strict 1-paragraph + confidence-graded findings * TACTICAL_PROMPT — operator-grade with Course of Action (COA) recommendations  The chosen prompt is selected via the `prompt_style` argument to `LLMManager.generate()`. Backends that ignore the prompt style are not affected; the new behaviour is opt-in for callers that care.", "id": "estorides_llm/intelligence_prompts.py", "kind": "module", "label": "intelligence_prompts.py", "language": "py", "sha256": "8ffcc6a74405cf42", "symbol_count": 1, "symbols": [{"doc": "Render a list of observation dicts into a context block for the LLM.\n\nThe previous implementation truncated at 3500 chars but did not\npreserve source ordering, so a re-run produced a different prompt\nand a different answer. This version sorts by source name (stable\norder) and truncates per source so a single 100KB crt.sh response\ncannot blow past the model's context window.\n\nA hard ``max_total_chars`` cap bounds the WHOLE block. Without it a\nwide run (dozens of sources) could produce a prompt that dwarfs the\nmodel's context window, and the model would only see the first blocks\n— silently dropping the data it actually needs to answer. The cap is\napplied in source order so the assessment never goes blind on a large\ncollection.\n\nIf the orchestrator has stamped an ontology verdict on an\nobservation (the `ontology` key), the formatted block carries a\none-line \"SANCTIONED — OFAC SDN match on <fields>\" warning that\nthe LLM is required to surface in its assessment.", "kind": "function", "line": 97, "name": "format_context", "signature": "def format_context(sources)"}]}, {"doc": "estorides_llm.manager ===================== Multi-backend LLM router with pluggable backends.  A `LLMBackend` is a small Protocol that knows how to talk to one provider (ollama, openai, anthropic, openrouter, …). Backends register themselves in `BACKENDS` and the manager walks the list in priority order, returning the first successful response.  This is the registry pattern in place of the old if/elif chain that hard-coded a method per backend. Adding a new provider is now: write a class, `@register(\"name\")` it, done. No edits to the manager.  System prompt lives in `intelligence_prompts.py` so multiple prompts (BLUF, tactical, standard) can be selected per call without forking the manager.", "id": "estorides_llm/manager.py", "kind": "module", "label": "manager.py", "language": "py", "sha256": "015f1bf0b3298933", "symbol_count": 22, "symbols": [{"doc": "Minimal contract for an LLM backend.\n\nImplementations MUST be total: raise on failure (the manager\ncatches and moves on) or return (\"\", \"\") to signal \"I can't\nanswer, try the next backend\".", "kind": "class", "line": 60, "name": "LLMBackend", "signature": "class LLMBackend(Protocol)"}, {"doc": "Decorator: register a backend under `name`.\n\nAccepts both an instance and a class. If a class is given, the\ndecorator instantiates it with no arguments — which is the\ncommon case for stateless backends that hold no per-instance\nstate. The class must therefore have a no-arg constructor.", "kind": "method", "line": 97, "name": "register", "signature": "def register(name)"}, {"kind": "class", "line": 120, "name": "OllamaBackend", "signature": "class OllamaBackend"}, {"doc": "Shared implementation for OpenAI-shaped APIs (openai, openrouter, …).\n\nSubclasses set `name`, `env_key`, and `base_url`.", "kind": "class", "line": 266, "name": "_OpenAICompatibleBackend", "signature": "class _OpenAICompatibleBackend"}, {"kind": "class", "line": 302, "name": "OpenAIBackend", "signature": "class OpenAIBackend(_OpenAICompatibleBackend)"}, {"kind": "class", "line": 309, "name": "OpenRouterBackend", "signature": "class OpenRouterBackend(_OpenAICompatibleBackend)"}, {"kind": "class", "line": 316, "name": "AnthropicBackend", "signature": "class AnthropicBackend"}, {"kind": "class", "line": 356, "name": "LLMManager", "signature": "class LLMManager"}, {"doc": "Return (content, model_id). Empty content means \"skip me\".", "kind": "method", "line": 69, "name": "__call__", "signature": "def __call__(self, prompt, context, max_tokens, temperature, request_timeout)"}, {"doc": "Optional: stream (kind, text) chunks. Base backends may skip.", "kind": "method", "line": 80, "name": "stream_generate", "signature": "def stream_generate(self, prompt, context, model, temperature, request_timeout)"}, {"kind": "method", "line": 105, "name": "deco", "signature": "def deco(backend_or_cls)"}, {"doc": "Return available ollama models and reachability status.", "kind": "method", "line": 124, "name": "get_status", "signature": "def get_status()"}, {"doc": "Pick a model ollama actually has pulled.\n\nIf ESTORIDES_OLLAMA_MODEL is set via env, use that (verify it's\npulled). Otherwise auto-detect from /api/tags and use the first\navailable model.", "kind": "method", "line": 134, "name": "_resolve_model", "signature": "def _resolve_model(self, request_timeout)"}, {"doc": "Stream an ollama response as (kind, text) chunks.\n\nkind ∈ {\"thinking\", \"content\"} so the UI can render the model's\nreasoning preamble into a collapsible \"thinking\" panel and stream\nthe final answer below it. Yields dicts, never raises; a hard\n``request_timeout`` guards the whole stream.", "kind": "method", "line": 188, "name": "stream_generate", "signature": "def stream_generate(self, prompt, context, model, temperature, request_timeout)"}, {"kind": "method", "line": 227, "name": "__call__", "signature": "def __call__(self, prompt, context, max_tokens, temperature, request_timeout)"}, {"kind": "method", "line": 274, "name": "__call__", "signature": "def __call__(self, prompt, context, max_tokens, temperature, request_timeout)"}, {"kind": "method", "line": 319, "name": "__call__", "signature": "def __call__(self, prompt, context, max_tokens, temperature, request_timeout)"}, {"kind": "method", "line": 357, "name": "__init__", "signature": "def __init__(self)"}, {"doc": "Try each backend in priority order; return the first that succeeds.\n\n`request_timeout` caps every backend's HTTP call so a slow\nlocal model cannot keep a worker thread alive past the\norchestrator's deadline. Returns a dict with keys:\nbackend, model, content, error.", "kind": "method", "line": 373, "name": "generate", "signature": "def generate(self, prompt)"}, {"doc": "Return ollama reachability and available models.", "kind": "method", "line": 414, "name": "get_ollama_status", "signature": "def get_ollama_status(self)"}, {"doc": "Stream an analysis from a specific ollama model.\n\nYields ``{\"kind\": \"thinking\"|\"content\", \"text\": …}`` chunks. The\nmodel name is treated as hostile: it must match an actual ollama\nmodel, else we raise before touching the network. If no model is\ngiven, we fall back to the auto-detected fast default.", "kind": "method", "line": 422, "name": "stream", "signature": "def stream(self, prompt)"}, {"kind": "method", "line": 459, "name": "_stub_response", "signature": "def _stub_response(self, prompt, context)"}]}, {"doc": "estorides.web ============= Flask app providing: * 2D map     (Leaflet) * knowledge graph (D3.js force-directed) * timeline * source results panel * multi-LLM analysis", "id": "estorides_web.py", "kind": "module", "label": "estorides_web.py", "language": "py", "sha256": "4389bd8ea15a7913", "symbol_count": 97, "symbols": [{"doc": "Wrap an event generator in an SSE `Response` with the shared headers.", "kind": "function", "line": 78, "name": "_sse_response", "signature": "def _sse_response(gen)"}, {"doc": "Guard a route on an optional service being wired.\n\nReplaces the ~30 hand-written `if x is None: return ..., 503` blocks\nscattered through `create_app`. `service` is captured at decoration\ntime (it is assigned once before the routes are declared).", "kind": "function", "line": 83, "name": "_provides", "signature": "def _provides(service, message)"}, {"doc": "Best-effort client IP extraction.\n\nTrusts X-Forwarded-For only when behind a known proxy\n(ESTORIDES_TRUST_PROXY=1). Without that, falls back to\n`request.remote_addr`. This avoids the classic\n\"set X-Forwarded-For to bypass rate limits\" mistake on a\ndirectly-exposed deployment.", "kind": "function", "line": 102, "name": "_client_ip", "signature": "def _client_ip()"}, {"doc": "Read an int query-string arg, falling back to `default` on parse error.\n\nGuards every endpoint that previously did `int(request.args.get(...))`\ndirectly, where a non-numeric value raised ValueError and surfaced as\nan unhandled 500 to the client.", "kind": "function", "line": 119, "name": "_arg_int", "signature": "def _arg_int(name, default)"}, {"doc": "Send `p` as an attachment, then nuke `tmpdir` regardless of outcome.\n\nUsed by /api/export/<fmt> so reports/ doesn't accumulate a copy of\nevery exported bundle — see issue #43. The `finally` runs even when\nthe client disconnects mid-stream, which is the realistic failure\ncase for the exhaustion attack.", "kind": "function", "line": 135, "name": "_send_and_cleanup", "signature": "def _send_and_cleanup(p, tmpdir)"}, {"doc": "A live deep-run cross-search whose events feed an SSE stream.\n\nWraps a `BufferedEventSink` (the engine writes to it) and a cooperative\nstop flag the UI can set. Status and terminal state are read straight\noff the sink so there is one source of truth.", "kind": "class", "line": 149, "name": "_RunStreamJob", "signature": "class _RunStreamJob"}, {"doc": "Timestamp-prefixed id so jobs sort chronologically.", "kind": "method", "line": 187, "name": "_new_stream_job_id", "signature": "def _new_stream_job_id()"}, {"doc": "Decorator: enforce per-IP rate limit, write an audit row either way.\n\nCatches the rate-limit denial BEFORE doing real work, so a flood\ncan't tie up the orchestrator. Audit row written for both allow\nand deny so the trail is complete.", "kind": "method", "line": 192, "name": "_rate_limit_decorator", "signature": "def _rate_limit_decorator()"}, {"doc": "GraphRAG context for the local-AI prompt (spec/graph_rag_search.md).\n\nFail-soft: any problem (no graph yet, bad data) yields \"\" so the\nanalysis prompt goes out unchanged. CWE-209: detail stays in logs.", "kind": "method", "line": 236, "name": "_graph_rag_block", "signature": "def _graph_rag_block(query, budget_tokens)"}, {"kind": "method", "line": 258, "name": "create_app", "signature": "def create_app()"}, {"kind": "method", "line": 1792, "name": "_serve_loop", "signature": "def _serve_loop()"}, {"doc": "Trim raw responses for the UI and reformat observations.", "kind": "method", "line": 1805, "name": "_shape_for_ui", "signature": "def _shape_for_ui(result)"}, {"kind": "method", "line": 90, "name": "deco", "signature": "def deco(view)"}, {"kind": "method", "line": 157, "name": "__init__", "signature": "def __init__(self, job_id, query, query_type, case_id)"}, {"kind": "method", "line": 166, "name": "stop", "signature": "def stop(self)"}, {"kind": "method", "line": 169, "name": "should_stop", "signature": "def should_stop(self)"}, {"kind": "method", "line": 173, "name": "status", "signature": "def status(self)"}, {"kind": "method", "line": 177, "name": "done", "signature": "def done(self)"}, {"kind": "method", "line": 199, "name": "deco", "signature": "def deco(view)"}, {"doc": "Return liveness without auth for container probes.", "kind": "method", "line": 294, "name": "healthz", "signature": "def healthz()"}, {"doc": "Return readiness based on source registry state.", "kind": "method", "line": 305, "name": "readyz", "signature": "def readyz()"}, {"doc": "Return Prometheus text counters for this process.", "kind": "method", "line": 319, "name": "metrics", "signature": "def metrics()"}, {"doc": "Return the generated OpenAPI document for this app.", "kind": "method", "line": 326, "name": "openapi_doc", "signature": "def openapi_doc()"}, {"kind": "method", "line": 331, "name": "index", "signature": "def index()"}, {"kind": "method", "line": 352, "name": "api_status", "signature": "def api_status()"}, {"kind": "method", "line": 358, "name": "api_ollama_status", "signature": "def api_ollama_status()"}, {"kind": "method", "line": 364, "name": "api_run", "signature": "def api_run()"}, {"kind": "method", "line": 428, "name": "api_lan_scope", "signature": "def api_lan_scope()"}, {"kind": "method", "line": 436, "name": "api_lan_scan", "signature": "def api_lan_scan()"}, {"kind": "method", "line": 474, "name": "api_graph", "signature": "def api_graph()"}, {"doc": "Return real-time feed points (quakes, fires, news) for the map.\n\nOptional query string:\n  bbox=min_lon,min_lat,max_lon,max_lat — drop points outside.\n  no_cache=1 — bypass the on-disk cache.", "kind": "method", "line": 577, "name": "api_feeds", "signature": "def api_feeds()"}, {"kind": "method", "line": 608, "name": "api_export", "signature": "def api_export(fmt)"}, {"kind": "method", "line": 686, "name": "api_cases_list", "signature": "def api_cases_list()"}, {"kind": "method", "line": 699, "name": "api_cases_get", "signature": "def api_cases_get(case_id)"}, {"kind": "method", "line": 713, "name": "api_cases_delete", "signature": "def api_cases_delete(case_id)"}, {"doc": "Bookmark a case from the UI.\n\nSets a `notes` prefix so the case is easy to spot in the cases\nlist, then echoes the updated case back. The store is\nappend-only for observations, but `notes` is a free-text column\nwe can overwrite. This is the \"I want to come back to this\"\ngesture: in v1 the only durable artefact was the case id; in\nv1.3 we want the user to be able to tag their wins.", "kind": "method", "line": 721, "name": "api_cases_save", "signature": "def api_cases_save(case_id)"}, {"doc": "Symmetric diff between two cases by entity (type, value).\n\nQuery string: ?a=<case_id>&b=<case_id>\nReturns the entities present in B but not in A (\"added\"), the\ninverse (\"removed\"), and the per-type breakdown. The UI uses\nthis to show \"what's new since last run\" without a re-query.", "kind": "method", "line": 744, "name": "api_cases_diff", "signature": "def api_cases_diff()"}, {"doc": "Cross-feed entity resolution (Osiris-style /resolve).\n\nExamples:\n  GET /api/intel/resolve?type=ip&id=1.1.1.1\n  GET /api/intel/resolve?type=person&id=Tim%20Cook\n  GET /api/intel/resolve?type=cve&id=CVE-2024-3094", "kind": "method", "line": 769, "name": "api_intel_resolve", "signature": "def api_intel_resolve()"}, {"doc": "Cypher query against the Kùzu persistent graph.\n\nExamples:\n  GET /api/intel/graph?q=MATCH%20(n%3AEnt)%20RETURN%20n.id%20LIMIT%2010", "kind": "method", "line": 808, "name": "api_intel_graph", "signature": "def api_intel_graph()"}, {"doc": "Stats for both the case store and the Kùzu graph.", "kind": "method", "line": 847, "name": "api_intel_stats", "signature": "def api_intel_stats()"}, {"doc": "One-glance dashboard of the fused, cross-run fact base.", "kind": "method", "line": 868, "name": "api_fusion_stats", "signature": "def api_fusion_stats()"}, {"doc": "The YAML source catalogue with accumulated fetch/ok counters.", "kind": "method", "line": 876, "name": "api_fusion_sources", "signature": "def api_fusion_sources()"}, {"doc": "Search fused entities.\n\nExample: GET /api/fusion/entities?q=google&type=domain&min_sources=2\n``min_sources`` is the fusion-native filter: only entities that at\nleast N distinct feeds corroborate.", "kind": "method", "line": 885, "name": "api_fusion_entities", "signature": "def api_fusion_entities()"}, {"doc": "Full fused view of one entity: provenance, properties, edges.\n\n``min_sources`` (default 2) also returns the corroborated properties:\nattributes that independent feeds agree on.", "kind": "method", "line": 906, "name": "api_fusion_entity", "signature": "def api_fusion_entity(eid)"}, {"kind": "method", "line": 924, "name": "api_fusion_analytics_entity_timeline", "signature": "def api_fusion_analytics_entity_timeline(eid)"}, {"kind": "method", "line": 934, "name": "api_fusion_analytics_entity_summary", "signature": "def api_fusion_analytics_entity_summary(eid)"}, {"kind": "method", "line": 944, "name": "api_fusion_analytics_source_stats", "signature": "def api_fusion_analytics_source_stats(source_name)"}, {"kind": "method", "line": 954, "name": "api_fusion_analytics_consensus", "signature": "def api_fusion_analytics_consensus(eid)"}, {"kind": "method", "line": 964, "name": "api_fusion_analytics_top_changed", "signature": "def api_fusion_analytics_top_changed()"}, {"doc": "Render the YAML source manager page.", "kind": "method", "line": 974, "name": "admin_sources", "signature": "def admin_sources()"}, {"doc": "Return every YAML source with full configuration.", "kind": "method", "line": 991, "name": "api_sources_yaml_list", "signature": "def api_sources_yaml_list()"}, {"doc": "Create a new YAML source.", "kind": "method", "line": 1017, "name": "api_sources_yaml_create", "signature": "def api_sources_yaml_create()"}, {"doc": "Update/replace a YAML source.", "kind": "method", "line": 1038, "name": "api_sources_yaml_update", "signature": "def api_sources_yaml_update(name)"}, {"doc": "Delete a YAML source.", "kind": "method", "line": 1057, "name": "api_sources_yaml_delete", "signature": "def api_sources_yaml_delete(name)"}, {"kind": "method", "line": 1074, "name": "api_fusion_analytics_corroboration_matrix", "signature": "def api_fusion_analytics_corroboration_matrix()"}, {"doc": "Resolve a username across known social media platforms.\n\nExample: GET /api/socmint/resolve?username=torvalds\n         GET /api/socmint/resolve?username=torvalds&platforms=github,keybase\n\nReturns a SocialMediaProfile with profile URLs for every platform.", "kind": "method", "line": 1088, "name": "api_socmint_resolve", "signature": "def api_socmint_resolve()"}, {"doc": "Return the list of all known social media platforms.", "kind": "method", "line": 1110, "name": "api_socmint_platforms", "signature": "def api_socmint_platforms()"}, {"doc": "Extract social media profile URLs from a text blob.\n\nBody: {\"text\": \"Follow me on Twitter: https://x.com/johndoe\"}", "kind": "method", "line": 1118, "name": "api_socmint_discover", "signature": "def api_socmint_discover()"}, {"doc": "List all watch targets.", "kind": "method", "line": 1161, "name": "api_watch_list", "signature": "def api_watch_list()"}, {"doc": "Create a new watch target.\n\nBody: {\"query\": \"example.com\", \"type\": \"domain\", \"interval\": 1440, \"channels\": [\"slack\"], \"notes\": \"...\"}", "kind": "method", "line": 1170, "name": "api_watch_create", "signature": "def api_watch_create()"}, {"kind": "method", "line": 1204, "name": "api_watch_get", "signature": "def api_watch_get(watch_id)"}, {"kind": "method", "line": 1216, "name": "api_watch_delete", "signature": "def api_watch_delete(watch_id)"}, {"kind": "method", "line": 1227, "name": "api_watch_enable", "signature": "def api_watch_enable(watch_id)"}, {"kind": "method", "line": 1240, "name": "api_watch_disable", "signature": "def api_watch_disable(watch_id)"}, {"kind": "method", "line": 1252, "name": "api_watch_history", "signature": "def api_watch_history(watch_id)"}, {"doc": "List configured alert channels and their status.", "kind": "method", "line": 1262, "name": "api_alerts_channels", "signature": "def api_alerts_channels()"}, {"doc": "Send a test alert to a channel.\n\nBody: {\"channel\": \"slack\"}", "kind": "method", "line": 1270, "name": "api_alerts_test", "signature": "def api_alerts_test()"}, {"kind": "method", "line": 1286, "name": "api_scheduler_status", "signature": "def api_scheduler_status()"}, {"doc": "List the transforms applicable to an entity type.\n\nExample: GET /api/transforms?type=ip", "kind": "method", "line": 1304, "name": "api_transforms", "signature": "def api_transforms()"}, {"doc": "Run one transform and return nodes/links for graph merge.\n\nBody: {\"transform_id\": \"...\", \"type\": \"ip\", \"value\": \"1.2.3.4\"}", "kind": "method", "line": 1318, "name": "api_transform_run", "signature": "def api_transform_run()"}, {"doc": "Stream one transform as SSE `node`/`link` events plus `done`.\n\nQuery: ?transform_id=...&type=...&value=... The D3 graph merges\nevents progressively instead of waiting for the full payload.", "kind": "method", "line": 1340, "name": "api_transform_stream", "signature": "def api_transform_stream()"}, {"kind": "method", "line": 1379, "name": "api_osiris_bgp", "signature": "def api_osiris_bgp()"}, {"kind": "method", "line": 1393, "name": "api_osiris_mac", "signature": "def api_osiris_mac()"}, {"kind": "method", "line": 1407, "name": "api_osiris_phone", "signature": "def api_osiris_phone()"}, {"kind": "method", "line": 1421, "name": "api_osiris_github", "signature": "def api_osiris_github()"}, {"kind": "method", "line": 1435, "name": "api_osiris_leaks", "signature": "def api_osiris_leaks()"}, {"kind": "method", "line": 1449, "name": "api_osiris_kev", "signature": "def api_osiris_kev()"}, {"kind": "method", "line": 1458, "name": "api_osiris_malware", "signature": "def api_osiris_malware()"}, {"kind": "method", "line": 1463, "name": "api_osiris_threats", "signature": "def api_osiris_threats()"}, {"kind": "method", "line": 1476, "name": "api_discover_start", "signature": "def api_discover_start()"}, {"kind": "method", "line": 1529, "name": "api_discover_jobs", "signature": "def api_discover_jobs()"}, {"kind": "method", "line": 1535, "name": "api_discover_stop", "signature": "def api_discover_stop()"}, {"doc": "Server-Sent Events for a discoverer job.\n\nThe browser opens `EventSource('/api/discover/stream?job_id=...')`\nand we keep the connection open, pushing one event per\nJSON line as the background worker discovers things. The\nstream closes when the job finishes (status=done|error|stopped).", "kind": "method", "line": 1547, "name": "api_discover_stream", "signature": "def api_discover_stream()"}, {"kind": "method", "line": 1597, "name": "api_run_stream_start", "signature": "def api_run_stream_start()"}, {"kind": "method", "line": 1669, "name": "api_run_stream_stop", "signature": "def api_run_stream_stop()"}, {"kind": "method", "line": 1681, "name": "api_run_stream", "signature": "def api_run_stream()"}, {"kind": "method", "line": 1728, "name": "api_analyze_stream", "signature": "def api_analyze_stream()"}, {"kind": "method", "line": 92, "name": "wrapper", "signature": "def wrapper()"}, {"kind": "method", "line": 201, "name": "wrapper", "signature": "def wrapper()"}, {"kind": "method", "line": 1356, "name": "_gen", "signature": "def _gen()"}, {"kind": "method", "line": 1560, "name": "gen", "signature": "def gen()"}, {"kind": "method", "line": 1632, "name": "_drive", "signature": "def _drive()"}, {"kind": "method", "line": 1687, "name": "gen", "signature": "def gen()"}, {"kind": "method", "line": 1741, "name": "_run", "signature": "def _run()"}, {"kind": "method", "line": 1763, "name": "gen", "signature": "def gen()"}, {"kind": "method", "line": 1137, "name": "_watch_runner", "signature": "def _watch_runner(swatch)"}, {"kind": "method", "line": 1351, "name": "_err", "signature": "def _err()"}]}, {"doc": "estorides_web_tools =================== Tools vertical slice extracted from the ``create_app`` God factory.  Owns the install-state registry and the four ``/api/tools*`` routes with identical URLs, auth, rate-limit events and behaviours. The factory registers :data:`tools_bp`; no tools view logic stays inline.  Import cycle note: this module is imported lazily inside ``create_app`` (call time, after ``estorides_web`` is fully loaded), so top-level imports of the factory helpers are safe.", "id": "estorides_web_tools.py", "kind": "module", "label": "estorides_web_tools.py", "language": "py", "sha256": "01800b0c333c9747", "symbol_count": 5, "symbols": [{"kind": "function", "line": 49, "name": "api_tools_list", "signature": "def api_tools_list()"}, {"kind": "function", "line": 68, "name": "api_tools_doctor", "signature": "def api_tools_doctor()"}, {"kind": "function", "line": 75, "name": "api_tool_install", "signature": "def api_tool_install(name)"}, {"kind": "function", "line": 120, "name": "api_tool_install_status", "signature": "def api_tool_install_status(name)"}, {"kind": "function", "line": 91, "name": "_worker", "signature": "def _worker()"}]}, {"doc": "Bootstrap a venv and install the runtime + optional test dependencies.  Idempotent: re-running on an existing venv is a no-op for the venv step. Tries the full install first (kuzu + aiohttp_socks + gunicorn), then degrades gracefully if a native dep fails to build (e.g. ARM Kali).  Usage: ./install.sh           install everything ./install.sh --minimal install only the minimum required deps ./install.sh --dev     also install test/lint extras", "id": "install.sh", "kind": "module", "label": "install.sh", "language": "sh", "sha256": "c9c13c802f201784", "symbol_count": 2, "symbols": [{"doc": "3) Two install passes: full first, minimal fallback. We don't want a single Cython build error to leave the user with a half-installed project — better to fall back to a working baseline and tell them what didn't make it.", "kind": "function", "line": 51, "name": "install_full"}, {"kind": "function", "line": 55, "name": "install_minimal"}]}, {"doc": "Estorides front-end controller", "id": "static/js/estorides.js", "kind": "module", "label": "estorides.js", "language": "js", "sha256": "11a9a939cd927528", "symbol_count": 180, "symbols": [{"doc": "Show/hide that respects the HTML5 `hidden` attribute. `[hidden]` is enforced with `!important` (see estorides_ui.css), so toggling inline `style.display` alone can never reveal an element that carries the attribute — the attribute must be cleared too.", "kind": "function", "line": 12, "name": "setVisible"}, {"kind": "function", "line": 24, "name": "isLanQueryLocal"}, {"kind": "function", "line": 33, "name": "lanRunFields"}, {"kind": "function", "line": 46, "name": "ensureLanConsent"}, {"doc": "--- UX helpers (v1.4) ----", "kind": "function", "line": 128, "name": "detectQueryTypeLocal"}, {"kind": "function", "line": 139, "name": "showToast"}, {"kind": "function", "line": 149, "name": "updateQueryChip"}, {"kind": "function", "line": 159, "name": "setRunProgress"}, {"kind": "function", "line": 180, "name": "showEmptyState"}, {"kind": "function", "line": 186, "name": "summariseObservation"}, {"kind": "function", "line": 211, "name": "buildResultCard"}, {"kind": "function", "line": 270, "name": "requestToolInstall"}, {"kind": "function", "line": 293, "name": "pollToolInstall"}, {"doc": "Turn a failed install result into a meaningful, non-\"unknown\" message.", "kind": "function", "line": 316, "name": "_installErrMsg"}, {"kind": "function", "line": 326, "name": "populateCategoryFilter"}, {"kind": "function", "line": 332, "name": "applyResultFilters"}, {"kind": "function", "line": 353, "name": "bindResultFilters"}, {"kind": "function", "line": 361, "name": "showFriendlyError"}, {"kind": "function", "line": 374, "name": "focusGraphNodeByValue"}, {"kind": "function", "line": 383, "name": "switchSidebarTab"}, {"kind": "function", "line": 387, "name": "switchCanvasTab"}, {"kind": "function", "line": 409, "name": "clearMap"}, {"kind": "function", "line": 414, "name": "plotPoints"}, {"doc": "Rebuild the geospatial + temporal views from everything seen so far. plotPoints clears and redraws from the full coord set, so feeding it the accumulated observations makes the map grow as sources resolve.", "kind": "function", "line": 518, "name": "replotStreamData"}, {"kind": "function", "line": 530, "name": "stopRunStream"}, {"doc": "Dedicated LAN entry point: resolves the scope (query box when it looks like LAN, otherwise the auto-detected private network), asks permission through the blocking modal, then runs a real local inventory scan. The checkbox only widens the ceiling to active; without it the scan still runs passive. Results render below and the graph views refresh from the fed knowledge graph.", "kind": "function", "line": 551, "name": "renderLanResults"}, {"kind": "function", "line": 620, "name": "startLanScan"}, {"kind": "function", "line": 685, "name": "runQuery"}, {"doc": "Blocking fallback: the original one-shot render path.", "kind": "function", "line": 773, "name": "runQueryBlocking"}, {"doc": "Deep-search an entity through the full OSINT pipeline without clearing existing data — appends and merges into current state.", "kind": "function", "line": 807, "name": "searchEntity"}, {"kind": "function", "line": 858, "name": "handleRunStreamEvent"}, {"kind": "function", "line": 896, "name": "appendStreamObservation"}, {"kind": "function", "line": 918, "name": "appendStreamEntity"}, {"kind": "function", "line": 947, "name": "clearAll"}, {"kind": "function", "line": 975, "name": "setStatus"}, {"doc": "--- result rendering ----", "kind": "function", "line": 981, "name": "renderResult"}, {"kind": "function", "line": 1029, "name": "loadAnalysisModels"}, {"kind": "function", "line": 1036, "name": "renderAnalysisModels"}, {"kind": "function", "line": 1048, "name": "makeModelPill"}, {"kind": "function", "line": 1061, "name": "renderAnalysis"}, {"doc": "Node-only Markdown renderer (CodeQL #38). Remote LLM text must never travel as an HTML string into an innerHTML sink: parse, sanitize and append nodes in one step so there is no string round-trip to flag.", "kind": "function", "line": 1076, "name": "renderMarkdownInto"}, {"kind": "function", "line": 1091, "name": "setThinkingVisible"}, {"kind": "function", "line": 1098, "name": "toggleThinking"}, {"kind": "function", "line": 1103, "name": "reanalyze"}, {"doc": "Analyse a single graph/fusion entity: seed the analysis context with the entity's own data (kept in the Results context so re-analysis uses it) and stream an assessment into the Analysis tab.", "kind": "function", "line": 1193, "name": "analyseEntity"}, {"kind": "function", "line": 1226, "name": "expandNode"}, {"doc": "Merge a /api/intel/resolve response into the current D3 graph and Leaflet map. Idempotent: re-clicking the same node won't duplicate edges. Returns {nodes, links} counts of what was actually added. Every merge pushes a batch onto window._graphBatches so Ctrl+Z (window.undoGraph) can pop it and repaint without that batch.", "kind": "function", "line": 1256, "name": "mergeExpansionIntoGraph"}, {"doc": "Re-draws the D3 graph with the original nodes/edges PLUS any extras passed in (from a /api/intel/resolve call). The extras are translated to the shape the drawGraph() function already understands (id, label, type, color, size).", "kind": "function", "line": 1320, "name": "drawGraphWithExtras"}, {"kind": "function", "line": 1344, "name": "pushLink"}, {"doc": "True while the force-graph module owns the canvas in 3D mode.", "kind": "function", "line": 1367, "name": "is3DActive"}, {"doc": "Map a graph node's type/kind onto a resolver/transform entity type.", "kind": "function", "line": 1395, "name": "resolverTypeFor"}, {"kind": "function", "line": 1411, "name": "saveLevelOverrides"}, {"kind": "function", "line": 1415, "name": "levelOf"}, {"kind": "function", "line": 1419, "name": "clusterColor"}, {"doc": "Cluster colors come from remote data (TELEMETRY.cluster_palette and per-cluster `color`). Treat them as hostile: only accept a CSS-safe hex or rgb() colour, else fall back to a neutral grey. This keeps a tampered value from ever carrying CSS/HTML into the graph overlays.", "kind": "function", "line": 1429, "name": "safeColor"}, {"doc": "Build a clusters[] summary from a flat node list (used after a merge when the server-side clusters array isn't carried along).", "kind": "function", "line": 1438, "name": "deriveClusters"}, {"doc": "--- floating overlays (tooltip + context menu) ----", "kind": "function", "line": 1451, "name": "hideTooltip"}, {"kind": "function", "line": 1465, "name": "purifyHTML"}, {"doc": "Append hostile markup: sanitize with DOMPurify, assign once. When the library is missing, fall back to inert plain text (fail-closed).", "kind": "function", "line": 1476, "name": "setSanitizedHTML"}, {"kind": "function", "line": 1485, "name": "showTooltipAt"}, {"kind": "function", "line": 1495, "name": "hideContextMenu"}, {"doc": "Cross-referenced tooltip for an inter-cluster (bridge) link.", "kind": "function", "line": 1500, "name": "showBridgeTooltip"}, {"kind": "function", "line": 1529, "name": "showNodeTooltip"}, {"doc": "--- context menu: transforms grouped by intel tier ----", "kind": "function", "line": 1548, "name": "showContextMenu"}, {"kind": "function", "line": 1623, "name": "setNodeLevel"}, {"doc": "Re-apply level rings to every rendered node circle.", "kind": "function", "line": 1632, "name": "applyLevelStyles"}, {"kind": "function", "line": 1640, "name": "focusNode"}, {"doc": "Run a graph pivot transform and merge the result into the graph+map.", "kind": "function", "line": 1654, "name": "runTransform"}, {"doc": "Stream a transform over SSE so the graph \"explodes\" progressively. Shift+click on a context-menu transform uses this path; plain click keeps the single-POST path above. Both merge through the same dedupe (_expansionSeen) and history (_graphBatches) machinery.", "kind": "function", "line": 1675, "name": "runTransformStream"}, {"doc": "Undo the last graph expansion (transform / resolve). Rebuilds the dedupe set from the surviving batches and repaints the base graph plus those extras — the popped batch vanishes from the canvas.", "kind": "function", "line": 1735, "name": "undoGraph"}, {"doc": "--- side inspector panel ----", "kind": "function", "line": 1770, "name": "selectNode"}, {"kind": "function", "line": 1781, "name": "add"}, {"kind": "function", "line": 1787, "name": "addText"}, {"doc": "--- unified force-graph renderer (clusters + rings + interactions) ----", "kind": "function", "line": 1874, "name": "renderGraphCore"}, {"kind": "function", "line": 1962, "name": "drawHulls"}, {"doc": "Low-level D3 redraw given a flat nodes/links list (back-compat shim).", "kind": "function", "line": 1993, "name": "_redrawGraph"}, {"kind": "function", "line": 1999, "name": "setStatusDot"}, {"kind": "function", "line": 2005, "name": "showWorkingIndicator"}, {"kind": "function", "line": 2010, "name": "hideWorkingIndicator"}, {"kind": "function", "line": 2015, "name": "toggleTierSection"}, {"kind": "function", "line": 2023, "name": "renderTieredResults"}, {"kind": "function", "line": 2089, "name": "escapeAttr"}, {"kind": "function", "line": 2125, "name": "buildMapCoords"}, {"kind": "function", "line": 2221, "name": "validCoord"}, {"kind": "function", "line": 2225, "name": "colorFor"}, {"kind": "function", "line": 2244, "name": "renderEntities"}, {"kind": "function", "line": 2296, "name": "renderGraphSummary"}, {"kind": "function", "line": 2325, "name": "colorForKind"}, {"kind": "function", "line": 2333, "name": "renderTimeline"}, {"kind": "function", "line": 2383, "name": "fmtTime"}, {"kind": "function", "line": 2394, "name": "filterTimeline"}, {"doc": "--- D3 graph view ----", "kind": "function", "line": 2443, "name": "drawGraph"}, {"kind": "function", "line": 2473, "name": "loadCases"}, {"doc": "Rich case modal: loads the saved DB record (query, entities, observations) and offers Analyse (re-run on the case data) and \"Load in workspace\" (restore entities into the tabs).", "kind": "function", "line": 2499, "name": "openCaseDetail"}, {"doc": "Restore a case's saved entities into the workspace tabs (entities list, graph summary, map, timeline) without re-running collection.", "kind": "function", "line": 2554, "name": "restoreCaseToWorkspace"}, {"doc": "Build map coords from a case's saved entities, reusing the same latitude/longitude resolution and country-centroid fallback as the normal run renderer so a case without explicit lat/lon still drops pins.", "kind": "function", "line": 2575, "name": "buildCaseMapCoords"}, {"kind": "function", "line": 2593, "name": "renderCaseItem"}, {"kind": "function", "line": 2619, "name": "debounce"}, {"doc": "--- utils ----", "kind": "function", "line": 2669, "name": "escapeHTML"}, {"kind": "function", "line": 2674, "name": "truncate"}, {"doc": "Bookmark a case. The endpoint prefixes the notes column with \"[saved]\" so the bookmarked case surfaces in the list at a glance.", "kind": "function", "line": 2697, "name": "caseActionSave"}, {"doc": "Compare this case to another. The user picks the baseline; the response is rendered inline in a diff panel under the case.", "kind": "function", "line": 2718, "name": "caseActionDiff"}, {"doc": "Render the diff result below the case. The panel survives until the user reloads the cases list (or opens another diff).", "kind": "function", "line": 2738, "name": "renderCaseDiffPanel"}, {"doc": "Render the Markdown report. We just dump the text into a modal overlay — keeping it in-browser is enough; the CLI command produces a file copy for sharing.", "kind": "function", "line": 2779, "name": "caseActionReport"}, {"kind": "function", "line": 2815, "name": "showReportModal"}, {"doc": "--- generic modal helpers (replace alert/prompt/confirm) ---- User input collected here is treated as hostile: coerced to a string and stripped of control chars before it goes into any request/HTML.", "kind": "function", "line": 2840, "name": "_sanitizeInput"}, {"kind": "function", "line": 2843, "name": "openModal"}, {"doc": "Promise-style text prompt. Resolves with a sanitized string or null.", "kind": "function", "line": 2878, "name": "promptModal"}, {"kind": "function", "line": 2902, "name": "confirmModal"}, {"doc": "Responsive sidebar toggle + resizable divider.", "kind": "function", "line": 2994, "name": "loadSidebarWidth"}, {"kind": "function", "line": 3005, "name": "saveSidebarWidth"}, {"kind": "function", "line": 3008, "name": "loadSidebarCollapsed"}, {"kind": "function", "line": 3016, "name": "saveSidebarCollapsed"}, {"doc": "--- Fusion tab ----", "kind": "function", "line": 3074, "name": "switchSidebarTab"}, {"kind": "function", "line": 3087, "name": "loadFusionTab"}, {"kind": "function", "line": 3093, "name": "loadFusionStats"}, {"kind": "function", "line": 3112, "name": "loadFusionTopChanged"}, {"kind": "function", "line": 3141, "name": "loadFusionSearch"}, {"kind": "function", "line": 3148, "name": "doSearch"}, {"kind": "function", "line": 3186, "name": "loadFusionEntityDetail"}, {"kind": "function", "line": 3258, "name": "_sseUrl"}, {"doc": "The discoverer code lives outside the IIFE, so the module-private setStatus is not in scope here. Provide a global one that writes to the", "kind": "function", "line": 3282, "name": "setStatus"}, {"kind": "function", "line": 3289, "name": "setDiscoverProgress"}, {"kind": "function", "line": 3301, "name": "hideDiscoverProgress"}, {"kind": "function", "line": 3307, "name": "startDiscover"}, {"kind": "function", "line": 3390, "name": "stopDiscover"}, {"kind": "function", "line": 3407, "name": "handleDiscoverEvent"}, {"kind": "function", "line": 3446, "name": "addDiscoverEntityToTab"}, {"kind": "function", "line": 3474, "name": "escapeHtml"}, {"kind": "function", "line": 3479, "name": "maybePlotDiscoverEntity"}, {"kind": "function", "line": 3486, "name": "flushDiscoverEntities"}, {"kind": "function", "line": 3505, "name": "check"}, {"kind": "function", "line": 25, "name": "t"}, {"kind": "function", "line": 60, "name": "safe"}, {"kind": "function", "line": 70, "name": "done"}, {"kind": "function", "line": 77, "name": "onYes"}, {"kind": "function", "line": 78, "name": "onNo"}, {"doc": "Headers may be a Headers instance, an object, or absent.", "kind": "function", "line": 105, "name": "set"}, {"kind": "function", "line": 114, "name": "TELEMETRY"}, {"kind": "function", "line": 215, "name": "toolBinary"}, {"kind": "function", "line": 221, "name": "status"}, {"kind": "function", "line": 319, "name": "out"}, {"kind": "function", "line": 333, "name": "text"}, {"kind": "function", "line": 334, "name": "cat"}, {"kind": "function", "line": 335, "name": "status"}, {"kind": "function", "line": 621, "name": "scope"}, {"kind": "function", "line": 662, "name": "msg"}, {"kind": "function", "line": 920, "name": "sig"}, {"doc": "The target being assessed = the current query box (if the user typed something) else the last completed run.", "kind": "function", "line": 1122, "name": "boxQ"}, {"kind": "function", "line": 1146, "name": "scheduleRender"}, {"kind": "function", "line": 1151, "name": "flush"}, {"kind": "function", "line": 1172, "name": "pump"}, {"kind": "function", "line": 1270, "name": "k"}, {"kind": "function", "line": 1388, "name": "CLUSTER_PALETTE"}, {"kind": "function", "line": 1421, "name": "c"}, {"kind": "function", "line": 1441, "name": "cid"}, {"kind": "function", "line": 1503, "name": "labelFor"}, {"kind": "function", "line": 1504, "name": "c"}, {"kind": "function", "line": 1598, "name": "tr"}, {"kind": "function", "line": 1644, "name": "barH"}, {"kind": "function", "line": 1681, "name": "flush"}, {"kind": "function", "line": 1748, "name": "k"}, {"kind": "function", "line": 1838, "name": "tr"}, {"kind": "function", "line": 1893, "name": "barH"}, {"kind": "function", "line": 2337, "name": "obs"}, {"kind": "function", "line": 2370, "name": "frac"}, {"kind": "function", "line": 2474, "name": "q"}, {"kind": "function", "line": 2500, "name": "entities"}, {"kind": "function", "line": 2501, "name": "obs"}, {"doc": "Saved cases get a visible bookmark pill so the operator can scan the list for \"things I came back to\" at a glance.", "kind": "function", "line": 2597, "name": "saved"}, {"kind": "function", "line": 2746, "name": "rows"}, {"kind": "function", "line": 2749, "name": "removed"}, {"kind": "function", "line": 2849, "name": "actions"}, {"kind": "function", "line": 2863, "name": "close"}, {"kind": "function", "line": 2964, "name": "tag"}, {"doc": "Auth token for SSE (EventSource can't set custom headers).", "kind": "function", "line": 3254, "name": "_sseAuthToken"}, {"doc": "Avoid duplicates with the simple in-memory check.", "kind": "function", "line": 3451, "name": "sig"}, {"doc": "Panels are keyed by the `<name>-canvas` class, not by id (the map panel's", "kind": "class", "line": 474, "name": "to"}, {"doc": "Geolocated entities (parsed.lat / parsed.lon) AND country codes. Many parsers stash coords on the entity itself (e.g. abuseipdb has a \"countryCode\" field). The whole point of v1.1 is to", "kind": "class", "line": 2178, "name": "on"}, {"kind": "class", "line": 3194, "name": "attribute"}]}, {"doc": "Estorides Bundles — circle 2D + sphere 3D hierarchical edge bundling.", "id": "static/js/graph_bundle.js", "kind": "module", "label": "graph_bundle.js", "language": "js", "sha256": "3ad4aaf8d3e63b3c", "symbol_count": 62, "symbols": [{"kind": "function", "line": 32, "name": "djb2KindColor"}, {"kind": "function", "line": 60, "name": "mk"}, {"kind": "function", "line": 66, "name": "clip"}, {"kind": "function", "line": 70, "name": "tabActive"}, {"kind": "function", "line": 74, "name": "ink"}, {"kind": "function", "line": 88, "name": "colorOf"}, {"kind": "function", "line": 94, "name": "keyOf"}, {"kind": "function", "line": 99, "name": "bspline"}, {"kind": "function", "line": 120, "name": "curve"}, {"kind": "function", "line": 137, "name": "buildCurves"}, {"kind": "function", "line": 148, "name": "resize"}, {"kind": "function", "line": 160, "name": "radius"}, {"kind": "function", "line": 164, "name": "center"}, {"kind": "function", "line": 165, "name": "makeProjector"}, {"kind": "function", "line": 175, "name": "depthAlpha"}, {"kind": "function", "line": 177, "name": "focusState"}, {"kind": "function", "line": 185, "name": "edgeState"}, {"kind": "function", "line": 205, "name": "nodeLit"}, {"kind": "function", "line": 216, "name": "strokeCurve"}, {"kind": "function", "line": 226, "name": "pointOn"}, {"kind": "function", "line": 229, "name": "g"}, {"kind": "function", "line": 231, "name": "draw"}, {"kind": "function", "line": 291, "name": "drawGroups"}, {"kind": "function", "line": 333, "name": "nodeRadius"}, {"kind": "function", "line": 337, "name": "screenNodes"}, {"kind": "function", "line": 343, "name": "drawNodes"}, {"kind": "function", "line": 355, "name": "labelSet"}, {"kind": "function", "line": 367, "name": "drawLabels"}, {"kind": "function", "line": 405, "name": "drawHud"}, {"kind": "function", "line": 411, "name": "add"}, {"kind": "function", "line": 420, "name": "hitNode"}, {"kind": "function", "line": 443, "name": "hitGroup"}, {"kind": "function", "line": 461, "name": "tipFor"}, {"kind": "function", "line": 483, "name": "flowsOf"}, {"kind": "function", "line": 492, "name": "rank"}, {"kind": "function", "line": 497, "name": "inspectInGraph"}, {"kind": "function", "line": 504, "name": "nodeButton"}, {"kind": "function", "line": 512, "name": "groupButton"}, {"kind": "function", "line": 519, "name": "listInto"}, {"kind": "function", "line": 529, "name": "renderPanel"}, {"kind": "function", "line": 613, "name": "renderLegend"}, {"kind": "function", "line": 645, "name": "focusNode"}, {"kind": "function", "line": 650, "name": "focusGroup"}, {"kind": "function", "line": 655, "name": "facing"}, {"kind": "function", "line": 661, "name": "aimAt"}, {"kind": "function", "line": 667, "name": "setView"}, {"kind": "function", "line": 671, "name": "setColor"}, {"kind": "function", "line": 675, "name": "setDir"}, {"kind": "function", "line": 679, "name": "setBeta"}, {"kind": "function", "line": 687, "name": "sync"}, {"kind": "function", "line": 697, "name": "writeHash"}, {"kind": "function", "line": 709, "name": "readHash"}, {"kind": "function", "line": 726, "name": "ingest"}, {"kind": "function", "line": 762, "name": "refresh"}, {"kind": "function", "line": 786, "name": "frame"}, {"kind": "function", "line": 812, "name": "init"}, {"kind": "function", "line": 913, "name": "search"}, {"kind": "function", "line": 275, "name": "t0"}, {"kind": "function", "line": 279, "name": "tt"}, {"kind": "function", "line": 306, "name": "mid"}, {"kind": "function", "line": 958, "name": "tag"}, {"kind": "function", "line": 78, "name": "v"}]}, {"doc": "Estorides force-graph module (spec/graph_force3d.md).", "id": "static/js/graph_force.js", "kind": "module", "label": "graph_force.js", "language": "js", "sha256": "adcaf650e1022c83", "symbol_count": 109, "symbols": [{"kind": "function", "line": 52, "name": "reducedMotion"}, {"kind": "function", "line": 57, "name": "toast"}, {"kind": "function", "line": 65, "name": "fail"}, {"kind": "function", "line": 72, "name": "settings"}, {"doc": "--- adapt /api/graph nodes to RAW force-graph shape (fallback when the server did not send data.force; mirrors graph_force.py) ----", "kind": "function", "line": 79, "name": "adaptLocal"}, {"kind": "function", "line": 137, "name": "currentRaw"}, {"kind": "function", "line": 153, "name": "rebuildRaw"}, {"kind": "function", "line": 179, "name": "indexRaw"}, {"kind": "function", "line": 218, "name": "lkey"}, {"kind": "function", "line": 219, "name": "colorOf"}, {"kind": "function", "line": 220, "name": "dimmed"}, {"kind": "function", "line": 221, "name": "hiddenKind"}, {"kind": "function", "line": 226, "name": "visiblePayload"}, {"kind": "function", "line": 275, "name": "computeHighlight"}, {"kind": "function", "line": 298, "name": "tip"}, {"kind": "function", "line": 315, "name": "refreshFamList"}, {"kind": "function", "line": 320, "name": "famAnchor"}, {"kind": "function", "line": 330, "name": "famOf"}, {"kind": "function", "line": 335, "name": "clusterForce"}, {"kind": "function", "line": 337, "name": "force"}, {"kind": "function", "line": 352, "name": "ringOf"}, {"kind": "function", "line": 360, "name": "radialForce"}, {"kind": "function", "line": 362, "name": "force"}, {"kind": "function", "line": 377, "name": "collideForce"}, {"kind": "function", "line": 379, "name": "force"}, {"kind": "function", "line": 402, "name": "radius"}, {"kind": "function", "line": 406, "name": "linkStrengthFn"}, {"doc": "var base = st.linkStrength; var src = l.source.id || l.source, tgt = l.target.id || l.target; if (l.type === 'layered_as') return S.layout === 'radial' ? 0 : base * 0.15; if (l.type === 'member_of') return S.layout === 'radial' ? base * 0.05 : base * 0.6; if (S.layout === 'cluster') { var a = byId[src], b = byId[tgt]; return a && b && famOf(a) === famOf(b) ? base : base * 0.08; } if (S.layout === 'radial') return base * 0.35; return base; }; } /* ---- 3D mount ----", "kind": "function", "line": 422, "name": "setEngineButtons"}, {"doc": "Exclusive render: exactly one engine owns the pixels. Entering 3D hides every D3 SVG layer; leaving 3D hides the stage and pauses the 3D loop so a hidden WebGL canvas can never stack over 2D or steal pointer events.", "kind": "function", "line": 435, "name": "show3DChrome"}, {"kind": "function", "line": 458, "name": "mount3D"}, {"kind": "function", "line": 546, "name": "startOverlayLoop"}, {"kind": "function", "line": 551, "name": "overlayTick"}, {"kind": "function", "line": 561, "name": "convexHullPts"}, {"kind": "function", "line": 564, "name": "cross"}, {"kind": "function", "line": 580, "name": "inflateHull"}, {"kind": "function", "line": 592, "name": "traceSmooth"}, {"kind": "function", "line": 610, "name": "rr"}, {"kind": "function", "line": 619, "name": "glyphPath"}, {"kind": "function", "line": 647, "name": "tierColorOf"}, {"kind": "function", "line": 651, "name": "drawGlyph3D"}, {"kind": "function", "line": 703, "name": "drawLabel3D"}, {"kind": "function", "line": 724, "name": "paintOverlay3D"}, {"kind": "function", "line": 839, "name": "stageLocal"}, {"kind": "function", "line": 845, "name": "hitNode3D"}, {"kind": "function", "line": 854, "name": "segDist"}, {"kind": "function", "line": 861, "name": "hitEdge3D"}, {"kind": "function", "line": 876, "name": "hoverAt3D"}, {"kind": "function", "line": 887, "name": "bindStagePointer"}, {"kind": "function", "line": 939, "name": "refresh3D"}, {"kind": "function", "line": 945, "name": "reload3D"}, {"kind": "function", "line": 950, "name": "resize3D"}, {"kind": "function", "line": 961, "name": "applyLayout3D"}, {"doc": "Click selects and inspects only. Double click, the Enter key or the Expand button pivots through the resolver. Single click never mutates the graph, so inspecting in 3D can no longer repaint a D3 layer on top of the scene.", "kind": "function", "line": 982, "name": "onSelect3D"}, {"kind": "function", "line": 999, "name": "expandSelected"}, {"kind": "function", "line": 1009, "name": "focusSelected"}, {"kind": "function", "line": 1020, "name": "copyDeepLink"}, {"kind": "function", "line": 1022, "name": "done"}, {"kind": "function", "line": 1030, "name": "reheat"}, {"kind": "function", "line": 1038, "name": "toggleBridges"}, {"doc": "PNG export composites the WebGL link field with the glyph overlay, so the snapshot matches the screen (native node spheres stay hidden).", "kind": "function", "line": 1048, "name": "snapshot3D"}, {"kind": "function", "line": 1074, "name": "toggleOrbit"}, {"doc": "Edge inspection in 3D: bridge edges open the cross-reference tooltip, plain edges report their relation. Hover only changes the cursor.", "kind": "function", "line": 1088, "name": "onEdge3D"}, {"kind": "function", "line": 1112, "name": "clearSelection"}, {"kind": "function", "line": 1125, "name": "focusFamily"}, {"kind": "function", "line": 1150, "name": "markFamilies"}, {"kind": "function", "line": 1156, "name": "hud"}, {"kind": "function", "line": 1160, "name": "stat"}, {"doc": "(S.bridgesOnly ? ' · bridges' : ''); box.appendChild(m); if (S.hl.length) box.appendChild(stat(S.hl.length, 'highlighted')); if (S.selected && byId[S.selected]) { var s = document.createElement('span'); s.appendChild(document.createTextNode('focus ')); var b = document.createElement('b'); b.textContent = short(byId[S.selected].label, 24); s.appendChild(b); box.appendChild(s); } } /* ---- legend: kind pills + community families ----", "kind": "function", "line": 1187, "name": "renderLegend"}, {"kind": "function", "line": 1241, "name": "applyFilters"}, {"kind": "function", "line": 1276, "name": "runSearch"}, {"kind": "function", "line": 1342, "name": "pickHit"}, {"doc": "} box.classList.add('open'); } function pickHit(i) { var x = hits[i]; if (!x) return; var box = $('gf-results'); if (box) box.classList.remove('open'); if (S.engine === '3d') onSelect3D(x.n, null); else if (x.n._src) B.selectNode(x.n._src); else B.selectNode(x.n); } /* ---- engine toggle ----", "kind": "function", "line": 1353, "name": "to3D"}, {"kind": "function", "line": 1362, "name": "to2D"}, {"kind": "function", "line": 1371, "name": "readHash"}, {"kind": "function", "line": 1386, "name": "writeHash"}, {"kind": "function", "line": 1393, "name": "syncIsolateBtn"}, {"kind": "function", "line": 1398, "name": "wireToolbar"}, {"kind": "function", "line": 1423, "name": "toggle"}, {"kind": "function", "line": 96, "name": "ordered"}, {"kind": "function", "line": 104, "name": "cid"}, {"kind": "function", "line": 105, "name": "fam"}, {"kind": "function", "line": 162, "name": "src"}, {"kind": "function", "line": 189, "name": "s"}, {"kind": "function", "line": 190, "name": "t"}, {"kind": "function", "line": 205, "name": "entities"}, {"kind": "function", "line": 233, "name": "links"}, {"kind": "function", "line": 234, "name": "s"}, {"kind": "function", "line": 235, "name": "t"}, {"kind": "function", "line": 246, "name": "s"}, {"kind": "function", "line": 247, "name": "t"}, {"kind": "function", "line": 253, "name": "s"}, {"kind": "function", "line": 254, "name": "t"}, {"kind": "function", "line": 259, "name": "s"}, {"kind": "function", "line": 260, "name": "t"}, {"kind": "function", "line": 327, "name": "a"}, {"kind": "function", "line": 392, "name": "m"}, {"kind": "function", "line": 788, "name": "col"}, {"kind": "function", "line": 816, "name": "topN"}, {"kind": "function", "line": 1138, "name": "s"}, {"kind": "function", "line": 1139, "name": "t"}, {"kind": "function", "line": 1249, "name": "nodes"}, {"kind": "function", "line": 1256, "name": "edges"}, {"kind": "function", "line": 1257, "name": "s"}, {"kind": "function", "line": 1258, "name": "t"}, {"kind": "function", "line": 1267, "name": "fn"}, {"kind": "function", "line": 1511, "name": "tag"}, {"kind": "function", "line": 21, "name": "short"}, {"kind": "function", "line": 25, "name": "esc"}, {"kind": "function", "line": 600, "name": "mid"}]}, {"doc": "Estorides Source Manager — form-based YAML editor", "id": "static/js/source_manager.js", "kind": "module", "label": "source_manager.js", "language": "js", "sha256": "230aeec0a8afb0f9", "symbol_count": 18, "symbols": [{"doc": "/* Estorides Source Manager — form-based YAML editor (function () { 'use strict'; /* ─── auth ───", "kind": "function", "line": 7, "name": "authHeaders"}, {"kind": "function", "line": 15, "name": "apiFetch"}, {"doc": "contact: $('field-contact'), logsQueries: $('field-logs-queries'), toolUrl: $('field-tool-url'), toolMethod: $('field-tool-method'), toolHeaders: $('field-tool-headers'), toolParams: $('field-tool-params'), toolBody: $('field-tool-body'), pagStrategy: $('field-pag-strategy'), pagLimit: $('field-pag-limit'), pagParam: $('field-pag-param'), pagCursorPath: $('field-pag-cursor-path'), }; /* ─── get checked tags ───", "kind": "function", "line": 67, "name": "getCheckedTags"}, {"kind": "function", "line": 74, "name": "setCheckedTags"}, {"doc": "var checks = container.querySelectorAll('input[type=\"checkbox\"]:checked'); return Array.from(checks).map(function (c) { return c.value; }); } function setCheckedTags(containerId, values) { var container = $(containerId); if (!container) return; var vals = new Set(values || []); container.querySelectorAll('input[type=\"checkbox\"]').forEach(function (c) { c.checked = vals.has(c.value); }); } /* ─── read form → source object ───", "kind": "function", "line": 84, "name": "readForm"}, {"doc": "try { var b = JSON.parse(fields.toolBody.value.trim() || '{}'); if (Object.keys(b).length) s.tool.body = b; } catch (e) {} Pagination var strat = fields.pagStrategy.value; if (strat) { s.pagination = { strategy: strat }; var lim = parseInt(fields.pagLimit.value, 10); if (lim > 0) s.pagination.limit = lim; if (fields.pagParam.value.trim()) s.pagination.param = fields.pagParam.value.trim(); if (fields.pagCursorPath.value.trim()) s.pagination.cursor_path = fields.pagCursorPath.value.trim(); } return s; } /* ─── write source object → form ───", "kind": "function", "line": 120, "name": "writeForm"}, {"doc": "fields.pagCursorPath.value = pag.cursor_path || ''; setCheckedTags('field-applies-to', s.applies_to); setCheckedTags('field-entity-hints', s.entity_hints); currentName = s.name; isDirty = false; formStatus.textContent = ''; formStatus.className = 'form-status'; deleteBtn.hidden = false; updateYamlPreview(); } /* ─── update YAML preview ───", "kind": "function", "line": 183, "name": "updateYamlPreview"}, {"doc": "updateYamlPreview(); } /* ─── update YAML preview ─── function updateYamlPreview() { try { var s = readForm(); yamlPreview.textContent = JSON.stringify(s, null, 2); } catch (e) { yamlPreview.textContent = '/* cannot render preview */'; } } /* ─── render source list ───", "kind": "function", "line": 193, "name": "renderList"}, {"doc": "var keyBadge = s.requires_key ? '<span class=\"src-item-key-badge\">key</span>' : ''; var sysBadge = s.kind === 'system_app' ? '<span class=\"src-item-sys-badge\">sys</span>' : ''; html += '<div class=\"src-item' + active + '\" data-name=\"' + escAttr(s.name) + '\">' + '<span class=\"src-item-icon ' + onOff + '\"></span>' + '<div class=\"src-item-info\">' + '<div class=\"src-item-name\">' + escHtml(s.name) + sysBadge + keyBadge + '</div>' + '<div class=\"src-item-cat\">' + escHtml(s.description || '') + '</div>' + '</div></div>'; }); }); listEl.innerHTML = html || '<div style=\"padding:20px;text-align:center;color:var(--text-2);font-size:13px;\">No sources match filter</div>'; } /* ─── helpers ───", "kind": "function", "line": 223, "name": "escHtml"}, {"kind": "function", "line": 224, "name": "escAttr"}, {"doc": "'<div class=\"src-item-info\">' + '<div class=\"src-item-name\">' + escHtml(s.name) + sysBadge + keyBadge + '</div>' + '<div class=\"src-item-cat\">' + escHtml(s.description || '') + '</div>' + '</div></div>'; }); }); listEl.innerHTML = html || '<div style=\"padding:20px;text-align:center;color:var(--text-2);font-size:13px;\">No sources match filter</div>'; } /* ─── helpers ─── function escHtml(s) { return String(s).replace(/[&<>\"]/g, function (m) { return ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;' })[m]; }); } function escAttr(s) { return String(s).replace(/\"/g, '&quot;'); } /* ─── toast ───", "kind": "function", "line": 227, "name": "toast"}, {"doc": "/* ─── helpers ─── function escHtml(s) { return String(s).replace(/[&<>\"]/g, function (m) { return ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;' })[m]; }); } function escAttr(s) { return String(s).replace(/\"/g, '&quot;'); } /* ─── toast ─── function toast(msg, kind) { var el = document.createElement('div'); el.className = 'toast ' + (kind || 'info'); el.textContent = msg; document.getElementById('toast-stack').appendChild(el); setTimeout(function () { if (el.parentNode) el.parentNode.removeChild(el); }, 4000); } /* ─── load sources ───", "kind": "function", "line": 236, "name": "loadSources"}, {"doc": "apiFetch('/api/sources/yaml').then(function (data) { sources = data.sources || []; srcCount.textContent = data.total + ' sources'; renderList(filterInput.value); If currently selected source still exists, keep it; otherwise clear if (currentName && !data.sources.some(function (s) { return s.name === currentName; })) { clearEditor(); } }).catch(function (err) { toast('Failed to load sources: ' + err.message, 'err'); }); } /* ─── clear editor ───", "kind": "function", "line": 251, "name": "clearEditor"}, {"doc": "}); } /* ─── clear editor ─── function clearEditor() { form.hidden = true; editorEmpty.hidden = false; writeForm(null); Remove active class from all list items listEl.querySelectorAll('.src-item.active').forEach(function (el) { el.classList.remove('active'); }); currentName = null; } /* ─── select source ───", "kind": "function", "line": 261, "name": "selectSource"}, {"doc": "/* ─── select source ─── function selectSource(name) { var s = sources.filter(function (s) { return s.name === name; })[0]; if (!s) return; form.hidden = false; editorEmpty.hidden = true; writeForm(s); listEl.querySelectorAll('.src-item.active').forEach(function (el) { el.classList.remove('active'); }); var item = listEl.querySelector('.src-item[data-name=\"' + escAttr(name) + '\"]'); if (item) item.classList.add('active'); } /* ─── save source ───", "kind": "function", "line": 273, "name": "saveSource"}, {"doc": "toast('Source \"' + data.name + '\" saved', 'ok'); formStatus.textContent = 'Saved'; formStatus.className = 'form-status ok'; loadSources(); Re-select the saved source selectSource(data.name); }).catch(function (err) { toast('Save failed: ' + err.message, 'err'); formStatus.textContent = 'Error: ' + err.message; formStatus.className = 'form-status err'; }); } /* ─── delete source ───", "kind": "function", "line": 304, "name": "deleteSource"}, {"doc": "overlay.remove(); apiFetch('/api/sources/yaml/' + encodeURIComponent(currentName), { method: 'DELETE' }).then(function () { toast('Source \"' + currentName + '\" deleted', 'ok'); clearEditor(); loadSources(); }).catch(function (err) { toast('Delete failed: ' + err.message, 'err'); }); }); document.getElementById('confirm-cancel').addEventListener('click', function () { overlay.remove(); }); overlay.addEventListener('click', function (e) { if (e.target === overlay) overlay.remove(); }); } /* ─── new source ───", "kind": "function", "line": 334, "name": "newSource"}, {"kind": "class", "line": 255, "name": "from"}]}, {"doc": "Pytest configuration and shared fixtures for the estorides test suite.", "id": "tests/conftest.py", "kind": "module", "label": "conftest.py", "language": "py", "sha256": "9773124e7863b1b1", "symbol_count": 0, "symbols": []}, {"doc": "Directory guard for property-based (hypothesis) tests.  ``hypothesis`` is a dev-only extra (``pip install -e \".[dev]\"``; CI installs it). Generic runners such as ``deploynator release`` (``python -m pytest tests/ -q`` with the system interpreter) only have runtime deps, so a hard ``from hypothesis import ...`` at module top turns into 13 collection *errors* that abort the whole deploy gate.  When the extra is missing (or broken: e.g. a leftover directory without ``__init__`` that imports as an empty namespace package) the property files are excluded from collection (``collect_ignore_glob`` — the pytest-native mechanism for this); when it is installed (CI / ``.[dev]``) this file is a no-op and the full fuzz matrix runs. ``pytest.importorskip`` cannot be used here: raised inside a conftest it surfaces as a collection *error*, not a skip.", "id": "tests/properties/conftest.py", "kind": "module", "label": "conftest.py", "language": "py", "sha256": "cfb40ea28a96c3af", "symbol_count": 0, "symbols": []}, {"doc": "Property-based invariants for estorides_core.change_detection.  Hypothesis fuzzing: random snapshots, must always satisfy the boundedness invariants declared in spec S12.  Run from the project root::  .venv/bin/pytest tests/properties/test_change_detection_properties.py -v", "id": "tests/properties/test_change_detection_properties.py", "kind": "module", "label": "test_change_detection_properties.py", "language": "py", "sha256": "7156a13f7a443807", "symbol_count": 8, "symbols": [{"kind": "function", "line": 69, "name": "test_scores_always_bounded", "signature": "def test_scores_always_bounded(before, after)"}, {"kind": "function", "line": 77, "name": "test_max_changes_respected", "signature": "def test_max_changes_respected(before, after)"}, {"kind": "function", "line": 86, "name": "test_id_is_16_char_hex", "signature": "def test_id_is_16_char_hex(before, after)"}, {"kind": "function", "line": 95, "name": "test_idempotent", "signature": "def test_idempotent(before, after)"}, {"kind": "function", "line": 105, "name": "test_first_run_reports_all_as_new", "signature": "def test_first_run_reports_all_as_new(after)"}, {"kind": "function", "line": 113, "name": "test_after_none_returns_empty", "signature": "def test_after_none_returns_empty(before)"}, {"kind": "function", "line": 121, "name": "test_before_vs_no_after_empty", "signature": "def test_before_vs_no_after_empty(entities)"}, {"doc": "Whatever the input, the summary fields must agree with the\n``changes`` list: ``total == len(changes)``, ``by_kind`` counts\neach kind exactly, ``score_max/mean`` are derived from the actual\nchanges, and ``entities_compared == len(before.entities)`` (or 0 if\n``before`` is None).", "kind": "function", "line": 132, "name": "test_summary_consistency", "signature": "def test_summary_consistency(before, after)"}]}, {"doc": "Property-based fuzz for `csp_safe_styles`.  Defends against a future contributor who: * re-introduces a `style=\"...\"` template literal in the JS, * adds a new template variable to index.html that could be injection vector for an inline style attribute, * \"fixes\" the CSP by adding `'unsafe-inline'` and silently regresses the security property.  Each property is run with at least 1000 examples (doctrine §6).", "id": "tests/properties/test_csp_safe_styles_properties.py", "kind": "module", "label": "test_csp_safe_styles_properties.py", "language": "py", "sha256": "98531726ecde49db", "symbol_count": 3, "symbols": [{"doc": "Hypothetical: a future patch appends the given string somewhere\nin the JS. The codebase should still be free of `style=\"…\"` in any\nbacktick-delimited template literal.\n\nStrategy: simulate the patch as the insertion landing in any random\nposition of the file, then re-check the invariant. With 1000\nrandom `insertion` x random offset combinations, the test exercises\nthe structural property: there is no `style=\"…\"` substring inside\nany backtick literal in the current file.", "kind": "function", "line": 58, "name": "test_js_never_gains_a_style_attribute_in_template_literal", "signature": "def test_js_never_gains_a_style_attribute_in_template_literal(insertion)"}, {"doc": "Hypothetical: a future patch adds the given string somewhere in\nthe template. The contract is that `style=\"…\"` does not exist in\nany static markup of `templates/index.html`.\n\nJinja expressions `{{ … }}` and `{% … %}` are stripped before\nscanning so we test the static part of the document.", "kind": "function", "line": 105, "name": "test_template_never_gains_a_style_attribute", "signature": "def test_template_never_gains_a_style_attribute(insertion)"}, {"doc": "Hypothetical: a future patch sets `style-src` to\n`'self' 'unsafe-hashes' https://unpkg.com 'unsafe-inline'`. The\nproperty-based test asserts the current CSP doesn't have\n`'unsafe-inline'` in style-src, and that any string containing\n`bad` in that position is rejected.\n\nWe assert the current state, not a future mutation. With 1000\nrandom `bad` values, the structural check fires: the style-src\nlist never contains the unsafe keyword.", "kind": "function", "line": 137, "name": "test_csp_style_src_never_gains_unsafe_inline", "signature": "def test_csp_style_src_never_gains_unsafe_inline(bad)"}]}, {"doc": "Property-based invariants for estorides_core.graph_bundle.  Fuzzing con 1000 ejemplos por propiedad (doctrina: input hostil). P1 totalidad (nunca crash fuera de TypeError + salida consistente), P2 determinismo byte-identico, P3 vacio, P4 settings acotados.", "id": "tests/properties/test_graph_bundle_properties.py", "kind": "module", "label": "test_graph_bundle_properties.py", "language": "py", "sha256": "8d59d2901780a614", "symbol_count": 6, "symbols": [{"kind": "function", "line": 32, "name": "force_strategy", "signature": "def force_strategy(draw)"}, {"kind": "function", "line": 82, "name": "_assert_consistent", "signature": "def _assert_consistent(out, raw)"}, {"doc": "P1 — jamas crash fuera de TypeError; salida consistente y JSON-safe.", "kind": "function", "line": 104, "name": "test_p1_total_and_consistent", "signature": "def test_p1_total_and_consistent(payload)"}, {"doc": "P2 — dos pasadas byte-identicas (o ambas TypeError).", "kind": "function", "line": 115, "name": "test_p2_deterministic", "signature": "def test_p2_deterministic(payload)"}, {"doc": "P3 — payload vacio siempre devuelve vacio sin raise.", "kind": "function", "line": 131, "name": "test_p3_empty_never_raises", "signature": "def test_p3_empty_never_raises(payloads)"}, {"doc": "P4 — settings con claves fijas y beta/perspective acotados.", "kind": "function", "line": 141, "name": "test_p4_settings_bounded", "signature": "def test_p4_settings_bounded(data)"}]}, {"doc": "Property-based invariants for estorides_core.hypothesis_engine.  Hypothesis fuzzing: random observations + entities, must always satisfy the boundedness invariants declared in spec S10.  Run from the project root::  .venv/bin/pytest tests/properties/test_hypothesis_engine_properties.py -v", "id": "tests/properties/test_hypothesis_engine_properties.py", "kind": "module", "label": "test_hypothesis_engine_properties.py", "language": "py", "sha256": "7b67b53ac0ae6fcb", "symbol_count": 9, "symbols": [{"kind": "function", "line": 54, "name": "test_scores_always_bounded", "signature": "def test_scores_always_bounded(observations, entities)"}, {"kind": "function", "line": 66, "name": "test_claim_length_under_cap", "signature": "def test_claim_length_under_cap(observations, entities)"}, {"kind": "function", "line": 77, "name": "test_reasoning_length_under_cap", "signature": "def test_reasoning_length_under_cap(observations, entities)"}, {"kind": "function", "line": 88, "name": "test_sources_sorted_unique", "signature": "def test_sources_sorted_unique(observations, entities)"}, {"kind": "function", "line": 99, "name": "test_id_is_deterministic_hex", "signature": "def test_id_is_deterministic_hex(observations, entities)"}, {"kind": "function", "line": 112, "name": "test_idempotent", "signature": "def test_idempotent(observations, entities)"}, {"kind": "function", "line": 123, "name": "test_max_hypotheses_caps_output", "signature": "def test_max_hypotheses_caps_output(observations, entities)"}, {"kind": "function", "line": 134, "name": "test_min_score_filters", "signature": "def test_min_score_filters(observations, entities)"}, {"kind": "function", "line": 147, "name": "test_hostile_observation_does_not_crash", "signature": "def test_hostile_observation_does_not_crash(observations, entities)"}]}, {"doc": "Property-based invariants for estorides_core.observation_models.  Each ``@given`` run exercises at least 1000 random examples by default (hypothesis derives the count from the example budget). The invariants are the executable translation of BDD scenario O9 in spec/observation_models.md.", "id": "tests/properties/test_observation_models_properties.py", "kind": "module", "label": "test_observation_models_properties.py", "language": "py", "sha256": "3b04e1205f21b5fe", "symbol_count": 7, "symbols": [{"kind": "function", "line": 45, "name": "meta_strategy", "signature": "def meta_strategy(draw)"}, {"kind": "function", "line": 60, "name": "obs_strategy", "signature": "def obs_strategy(draw)"}, {"kind": "function", "line": 76, "name": "entity_strategy", "signature": "def entity_strategy(draw)"}, {"kind": "function", "line": 90, "name": "test_observation_round_trip_stability", "signature": "def test_observation_round_trip_stability(payload)"}, {"kind": "function", "line": 103, "name": "test_observation_bounded_fields", "signature": "def test_observation_bounded_fields(payload)"}, {"kind": "function", "line": 115, "name": "test_entity_round_trip_and_bounds", "signature": "def test_entity_round_trip_and_bounds(payload)"}, {"kind": "function", "line": 128, "name": "test_meta_never_echoes_unbounded_url", "signature": "def test_meta_never_echoes_unbounded_url(metas)"}]}, {"doc": "Property-based fuzzing for the parser totality contract (doctrine §6).  Every registered parser must be total: any JSON-shaped input returns a value instead of raising. We fuzz all 75 parsers with arbitrary nested primitives, lists and dicts (1000 examples per property).", "id": "tests/properties/test_parsers_properties.py", "kind": "module", "label": "test_parsers_properties.py", "language": "py", "sha256": "4ed6057f463bfb22", "symbol_count": 1, "symbols": [{"kind": "function", "line": 35, "name": "test_all_parsers_are_total", "signature": "def test_all_parsers_are_total(payload)"}]}, {"doc": "Property-based fuzzing for recon_fusion module (doctrine section 6).  Verifies invariants hold for arbitrary valid inputs. Minimum 1000 examples per property.", "id": "tests/properties/test_recon_fusion_properties.py", "kind": "module", "label": "test_recon_fusion_properties.py", "language": "py", "sha256": "715e0f13ca0b8ce6", "symbol_count": 17, "symbols": [{"doc": "P1 -- Scores are always in [0, 1].", "kind": "class", "line": 48, "name": "TestPropertyScoreBounds", "signature": "class TestPropertyScoreBounds"}, {"doc": "P2 -- total_observations and total_entities match input.", "kind": "class", "line": 68, "name": "TestPropertyTotalCounts", "signature": "class TestPropertyTotalCounts"}, {"doc": "P3 -- tier_summary counts match actual tier list lengths.", "kind": "class", "line": 93, "name": "TestPropertyTierSumMatches", "signature": "class TestPropertyTierSumMatches"}, {"doc": "P4 -- Same input always produces same output (time-independent fields excluded).", "kind": "class", "line": 114, "name": "TestPropertyDeterminism", "signature": "class TestPropertyDeterminism"}, {"doc": "P5 -- No duplicate canonical_id within the same tier.", "kind": "class", "line": 135, "name": "TestPropertyNoDuplicates", "signature": "class TestPropertyNoDuplicates"}, {"doc": "P6 -- result.tiers dict preserves canonical tier key order.", "kind": "class", "line": 155, "name": "TestPropertyTierKeysOrder", "signature": "class TestPropertyTierKeysOrder"}, {"doc": "P7 -- Empty query always raises ValueError.", "kind": "class", "line": 176, "name": "TestPropertyEmptyQueryRejected", "signature": "class TestPropertyEmptyQueryRejected"}, {"doc": "P8 -- None observations/entities never crash.", "kind": "class", "line": 196, "name": "TestPropertySafeWithBadInputs", "signature": "class TestPropertySafeWithBadInputs"}, {"kind": "method", "line": 58, "name": "test_all_scores_in_unit_interval", "signature": "def test_all_scores_in_unit_interval(self, query, query_type, observations, entities)"}, {"kind": "method", "line": 78, "name": "test_counts_match_input", "signature": "def test_counts_match_input(self, query, query_type, n_obs, n_ents)"}, {"kind": "method", "line": 103, "name": "test_tier_summary_matches", "signature": "def test_tier_summary_matches(self, query, query_type, observations, entities)"}, {"kind": "method", "line": 124, "name": "test_deterministic_output", "signature": "def test_deterministic_output(self, query, query_type, observations, entities)"}, {"kind": "method", "line": 145, "name": "test_no_duplicate_ids_in_tier", "signature": "def test_no_duplicate_ids_in_tier(self, query, query_type, observations, entities)"}, {"kind": "method", "line": 165, "name": "test_tier_keys_in_canonical_order", "signature": "def test_tier_keys_in_canonical_order(self, query, query_type, observations, entities)"}, {"kind": "method", "line": 185, "name": "test_empty_query_raises", "signature": "def test_empty_query_raises(self, query_type, observations, entities)"}, {"kind": "method", "line": 201, "name": "test_none_inputs_safe", "signature": "def test_none_inputs_safe(self, query, query_type)"}, {"kind": "method", "line": 214, "name": "test_entities_none_is_safe", "signature": "def test_entities_none_is_safe(self, query, query_type, observations)"}]}, {"doc": "Property-based invariants for estorides_core.reliability_scoring.  Hypothesis replaces libFuzzer/AFL for Python. These tests run >= 1000 random examples per property and must never crash, raise (except ``InvalidArgument`` from input validation), or violate the invariants.  Run from the project root::  .venv/bin/pytest tests/properties/test_reliability_scoring_properties.py -v", "id": "tests/properties/test_reliability_scoring_properties.py", "kind": "module", "label": "test_reliability_scoring_properties.py", "language": "py", "sha256": "78c3194e8a3b640c", "symbol_count": 12, "symbols": [{"kind": "function", "line": 56, "name": "test_score_always_bounded", "signature": "def test_score_always_bounded(reliability, credibility, corroboration, age, base, half_life)"}, {"kind": "function", "line": 73, "name": "test_corroboration_weight_in_unit_interval", "signature": "def test_corroboration_weight_in_unit_interval(n)"}, {"kind": "function", "line": 87, "name": "test_freshness_monotone_in_age", "signature": "def test_freshness_monotone_in_age(age1, age2)"}, {"kind": "function", "line": 110, "name": "test_reliability_from_name_never_raises", "signature": "def test_reliability_from_name_never_raises(name)"}, {"kind": "function", "line": 126, "name": "test_merge_confidence_bounded", "signature": "def test_merge_confidence_bounded(existing, new_obs, new_rel, new_cred, cor, age)"}, {"kind": "function", "line": 141, "name": "test_reliability_weight_set_is_curated", "signature": "def test_reliability_weight_set_is_curated()"}, {"kind": "function", "line": 146, "name": "test_credibility_weight_set_is_curated", "signature": "def test_credibility_weight_set_is_curated()"}, {"kind": "function", "line": 159, "name": "test_corroboration_is_monotone_in_count", "signature": "def test_corroboration_is_monotone_in_count(n1, n2)"}, {"kind": "function", "line": 184, "name": "test_higher_reliability_dominates", "signature": "def test_higher_reliability_dominates(rel1, rel2)"}, {"kind": "function", "line": 202, "name": "test_source_type_from_name_never_raises", "signature": "def test_source_type_from_name_never_raises(name)"}, {"kind": "function", "line": 219, "name": "test_source_type_weight_always_curated", "signature": "def test_source_type_weight_always_curated(reliability, credibility, source_type, corroboration, age, base, half_life)"}, {"kind": "function", "line": 241, "name": "test_source_type_weight_set_is_curated", "signature": "def test_source_type_weight_set_is_curated()"}]}, {"doc": "Property-based fuzzing for `search_telemetry` (spec S12 + predicate laws).  Each property runs >= 1000 random examples (doctrine section 6). The progress computation must be total over the integers (never raise on numeric input) and keep its bounds invariant; the brand/emoji predicates must be total over arbitrary text and never raise.", "id": "tests/properties/test_search_telemetry_properties.py", "kind": "module", "label": "test_search_telemetry_properties.py", "language": "py", "sha256": "606610bff9a4b767", "symbol_count": 6, "symbols": [{"kind": "function", "line": 32, "name": "test_progress_invariants_hold", "signature": "def test_progress_invariants_hold(completed, total, phase_key)"}, {"kind": "function", "line": 46, "name": "test_progress_rejects_unknown_phase", "signature": "def test_progress_rejects_unknown_phase(phase_key)"}, {"kind": "function", "line": 56, "name": "test_brand_predicate_is_total", "signature": "def test_brand_predicate_is_total(text)"}, {"kind": "function", "line": 65, "name": "test_emoji_predicate_is_total", "signature": "def test_emoji_predicate_is_total(text)"}, {"kind": "function", "line": 74, "name": "test_percent_encoded_emoji_predicate_is_total", "signature": "def test_percent_encoded_emoji_predicate_is_total(text)"}, {"kind": "function", "line": 84, "name": "test_brand_predicate_flags_embedded_brand", "signature": "def test_brand_predicate_flags_embedded_brand(prefix, suffix)"}]}, {"doc": "Property-based invariants for estorides_core.source_health_monitoring.  Hypothesis replaces libFuzzer/AFL for Python. These tests run >= 1000 random examples per property.", "id": "tests/properties/test_source_health_monitoring_properties.py", "kind": "module", "label": "test_source_health_monitoring_properties.py", "language": "py", "sha256": "0dda706d5e34d317", "symbol_count": 7, "symbols": [{"doc": "Build a valid SourceHealthInput, clamping ok <= fetch.", "kind": "function", "line": 21, "name": "_valid_input", "signature": "def _valid_input(fetch, ok, latency, last_seen, now)"}, {"kind": "function", "line": 49, "name": "test_health_score_always_bounded", "signature": "def test_health_score_always_bounded(fetch, ok, latency, last_seen, now)"}, {"kind": "function", "line": 63, "name": "test_status_always_valid_enum", "signature": "def test_status_always_valid_enum(fetch, ok, latency, last_seen, now)"}, {"kind": "function", "line": 78, "name": "test_success_rate_bounds", "signature": "def test_success_rate_bounds(fetch, ok, latency, last_seen, now)"}, {"kind": "function", "line": 89, "name": "test_unknown_when_below_min_fetches", "signature": "def test_unknown_when_below_min_fetches(fetch, config_min)"}, {"kind": "function", "line": 97, "name": "valid_health_inputs", "signature": "def valid_health_inputs(draw)"}, {"doc": "Dashboard summary counts must sum to total.", "kind": "function", "line": 112, "name": "test_dashboard_summary_counts_match", "signature": "def test_dashboard_summary_counts_match(records)"}]}, {"doc": "Property-based invariants for estorides_core.system_app_sources.  Hypothesis replaces libFuzzer/AFL. >= 1000 random examples per property. Run from the project root::  .venv/bin/pytest tests/properties/test_system_app_sources_properties.py -v", "id": "tests/properties/test_system_app_sources_properties.py", "kind": "module", "label": "test_system_app_sources_properties.py", "language": "py", "sha256": "68223da93778d2b5", "symbol_count": 6, "symbols": [{"kind": "function", "line": 37, "name": "test_tool_parsers_never_raise", "signature": "def test_tool_parsers_never_raise(parser_name, blob)"}, {"kind": "function", "line": 55, "name": "test_render_args_safe_inputs_no_metachars", "signature": "def test_render_args_safe_inputs_no_metachars(args, query, outdir)"}, {"kind": "function", "line": 70, "name": "test_render_args_substitution_is_verbatim", "signature": "def test_render_args_substitution_is_verbatim(query, outdir)"}, {"kind": "function", "line": 83, "name": "test_read_capped_respects_limit", "signature": "def test_read_capped_respects_limit(blob, cap)"}, {"kind": "function", "line": 110, "name": "test_parse_tool_output_never_raises", "signature": "def test_parse_tool_output_never_raises(parser_name, data)"}, {"kind": "function", "line": 124, "name": "test_adversarial_query_rejected_at_runner_boundary", "signature": "def test_adversarial_query_rejected_at_runner_boundary(prefix, bad, suffix)"}]}, {"id": "tests/properties/test_target_management_properties.py", "kind": "module", "label": "test_target_management_properties.py", "language": "py", "sha256": "03cb08ffced7a971", "symbol_count": 10, "symbols": [{"kind": "function", "line": 21, "name": "test_p1_add_target_never_raises", "signature": "def test_p1_add_target_never_raises(etype, value)"}, {"kind": "function", "line": 31, "name": "test_p2_validated_id_is_deterministic", "signature": "def test_p2_validated_id_is_deterministic(etype, value)"}, {"kind": "function", "line": 40, "name": "test_p3_make_target_id_stable_under_case", "signature": "def test_p3_make_target_id_stable_under_case(etype, value)"}, {"kind": "function", "line": 56, "name": "test_p4_valid_domains_validate", "signature": "def test_p4_valid_domains_validate(d)"}, {"kind": "function", "line": 68, "name": "test_p5_valid_ipv4_validate", "signature": "def test_p5_valid_ipv4_validate(ip)"}, {"kind": "function", "line": 77, "name": "test_p6_valid_emails_validate", "signature": "def test_p6_valid_emails_validate(email)"}, {"kind": "function", "line": 83, "name": "test_p7_auto_detect_never_fails", "signature": "def test_p7_auto_detect_never_fails(value)"}, {"kind": "function", "line": 89, "name": "test_p8_validate_target_never_raises", "signature": "def test_p8_validate_target_never_raises(value)"}, {"kind": "function", "line": 105, "name": "test_p9_batch_import_idempotent", "signature": "def test_p9_batch_import_idempotent(targets)"}, {"kind": "function", "line": 116, "name": "test_p10_batch_import_never_raises", "signature": "def test_p10_batch_import_never_raises(text)"}]}, {"doc": "Property-based invariants for estorides_core.tool_runner.  Hypothesis replaces libFuzzer/AFL for Python. These tests run >= 1000 random examples per property and must never crash, raise (except ``InvalidArgument`` from input validation), or violate the invariants.  Run from the project root::  .venv/bin/pytest tests/properties/test_tool_runner_properties.py -v", "id": "tests/properties/test_tool_runner_properties.py", "kind": "module", "label": "test_tool_runner_properties.py", "language": "py", "sha256": "ef8d68f9fed1c4a9", "symbol_count": 3, "symbols": [{"kind": "function", "line": 35, "name": "test_check_injection_safe_strings_silent", "signature": "def test_check_injection_safe_strings_silent(args)"}, {"kind": "function", "line": 46, "name": "test_check_injection_detects_all_metacharacters", "signature": "def test_check_injection_detects_all_metacharacters(prefix, bad, suffix)"}, {"kind": "function", "line": 63, "name": "test_run_tool_never_raises", "signature": "def test_run_tool_never_raises(target)"}]}, {"doc": "ATDD + BDD tests for estorides_core.active_recon.  Implements the Given-When-Then contracts declared in ``spec/active_recon.md``. Property-based invariants live in ``tests/properties/test_active_recon_properties.py``.", "id": "tests/test_active_recon.py", "kind": "module", "label": "test_active_recon.py", "language": "py", "sha256": "071990e6f905e0a4", "symbol_count": 24, "symbols": [{"kind": "class", "line": 24, "name": "TestNmapResult", "signature": "class TestNmapResult"}, {"kind": "class", "line": 45, "name": "TestNiktoResult", "signature": "class TestNiktoResult"}, {"kind": "class", "line": 58, "name": "TestSqlmapResult", "signature": "class TestSqlmapResult"}, {"kind": "class", "line": 71, "name": "TestDnsreconResult", "signature": "class TestDnsreconResult"}, {"kind": "class", "line": 84, "name": "TestTheHarvesterResult", "signature": "class TestTheHarvesterResult"}, {"kind": "class", "line": 97, "name": "TestResultTypes", "signature": "class TestResultTypes"}, {"kind": "class", "line": 135, "name": "TestErrorResultsFlowThrough", "signature": "class TestErrorResultsFlowThrough"}, {"kind": "method", "line": 25, "name": "test_run_nmap_returns_result", "signature": "def test_run_nmap_returns_result(self)"}, {"kind": "method", "line": 29, "name": "test_nmap_result_has_to_dict", "signature": "def test_nmap_result_has_to_dict(self)"}, {"kind": "method", "line": 38, "name": "test_nmap_result_to_entities_is_list", "signature": "def test_nmap_result_to_entities_is_list(self)"}, {"kind": "method", "line": 46, "name": "test_run_nikto_returns_result", "signature": "def test_run_nikto_returns_result(self)"}, {"kind": "method", "line": 50, "name": "test_nikto_result_has_to_dict", "signature": "def test_nikto_result_has_to_dict(self)"}, {"kind": "method", "line": 59, "name": "test_run_sqlmap_returns_result", "signature": "def test_run_sqlmap_returns_result(self)"}, {"kind": "method", "line": 63, "name": "test_sqlmap_result_has_to_dict", "signature": "def test_sqlmap_result_has_to_dict(self)"}, {"kind": "method", "line": 72, "name": "test_run_dnsrecon_returns_result", "signature": "def test_run_dnsrecon_returns_result(self)"}, {"kind": "method", "line": 76, "name": "test_dnsrecon_result_has_to_dict", "signature": "def test_dnsrecon_result_has_to_dict(self)"}, {"kind": "method", "line": 85, "name": "test_run_theHarvester_returns_result", "signature": "def test_run_theHarvester_returns_result(self)"}, {"kind": "method", "line": 89, "name": "test_harvester_result_has_to_dict", "signature": "def test_harvester_result_has_to_dict(self)"}, {"kind": "method", "line": 98, "name": "test_nmap_result_is_dataclass", "signature": "def test_nmap_result_is_dataclass(self)"}, {"kind": "method", "line": 106, "name": "test_nikto_result_is_dataclass", "signature": "def test_nikto_result_is_dataclass(self)"}, {"kind": "method", "line": 113, "name": "test_sqlmap_result_is_dataclass", "signature": "def test_sqlmap_result_is_dataclass(self)"}, {"kind": "method", "line": 120, "name": "test_dnsrecon_result_is_dataclass", "signature": "def test_dnsrecon_result_is_dataclass(self)"}, {"kind": "method", "line": 127, "name": "test_harvester_result_is_dataclass", "signature": "def test_harvester_result_is_dataclass(self)"}, {"kind": "method", "line": 136, "name": "test_nmap_error_result_has_empty_entities", "signature": "def test_nmap_error_result_has_empty_entities(self)"}]}, {"doc": "BDD tests for egress anonymisation wiring (`estorides_core.async_client`).  Ported from the standalone ``_test_proxy.py`` validator into pytest. Offline: no socket is opened.", "id": "tests/test_async_client.py", "kind": "module", "label": "test_async_client.py", "language": "py", "sha256": "238340a30a6fcf1e", "symbol_count": 15, "symbols": [{"kind": "class", "line": 16, "name": "TestSocksDetection", "signature": "class TestSocksDetection"}, {"kind": "class", "line": 23, "name": "TestRedaction", "signature": "class TestRedaction"}, {"kind": "class", "line": 33, "name": "TestEffectiveProxies", "signature": "class TestEffectiveProxies"}, {"kind": "class", "line": 45, "name": "TestRotation", "signature": "class TestRotation"}, {"kind": "class", "line": 63, "name": "TestFailClosed", "signature": "class TestFailClosed"}, {"kind": "method", "line": 17, "name": "test_schemes", "signature": "def test_schemes(self)"}, {"kind": "method", "line": 24, "name": "test_hides_credentials_keeps_host", "signature": "def test_hides_credentials_keeps_host(self)"}, {"kind": "method", "line": 29, "name": "test_passthrough_without_credentials", "signature": "def test_passthrough_without_credentials(self)"}, {"kind": "method", "line": 34, "name": "test_explicit_wins", "signature": "def test_explicit_wins(self, monkeypatch)"}, {"kind": "method", "line": 39, "name": "test_none_by_default", "signature": "def test_none_by_default(self, monkeypatch)"}, {"kind": "method", "line": 46, "name": "test_round_robin", "signature": "def test_round_robin(self)"}, {"kind": "method", "line": 57, "name": "test_direct_has_no_proxy", "signature": "def test_direct_has_no_proxy(self)"}, {"kind": "method", "line": 64, "name": "test_socks_without_backend_raises", "signature": "def test_socks_without_backend_raises(self, monkeypatch)"}, {"kind": "method", "line": 49, "name": "_enter_and_rotate", "signature": "def _enter_and_rotate()"}, {"kind": "method", "line": 68, "name": "_enter_socks", "signature": "def _enter_socks()"}]}, {"doc": "AuditLog rotation: file size cap, in-place .N rotation (issue #48).", "id": "tests/test_audit_log.py", "kind": "module", "label": "test_audit_log.py", "language": "py", "sha256": "e1aed23fd1bf9cd0", "symbol_count": 5, "symbols": [{"kind": "function", "line": 12, "name": "_ev", "signature": "def _ev(ts)"}, {"kind": "function", "line": 22, "name": "test_audit_log_appends", "signature": "def test_audit_log_appends(tmp_path)"}, {"kind": "function", "line": 31, "name": "test_audit_log_rotates_when_cap_exceeded", "signature": "def test_audit_log_rotates_when_cap_exceeded(tmp_path)"}, {"kind": "function", "line": 48, "name": "test_audit_log_rotation_respects_keep_count", "signature": "def test_audit_log_rotation_respects_keep_count(tmp_path)"}, {"kind": "function", "line": 61, "name": "test_audit_log_no_rotation_when_disabled", "signature": "def test_audit_log_no_rotation_when_disabled(tmp_path)"}]}, {"doc": "Auth gate: bearer-token protection for sensitive /api/* routes.  When `ESTORIDES_AUTH_TOKEN` is set, the gate is on: anonymous callers get 401, callers with the right bearer token get through. When unset, the gate is off and every call passes through (local-trust single-user mode).", "id": "tests/test_auth_gate.py", "kind": "module", "label": "test_auth_gate.py", "language": "py", "sha256": "5e2d7555e23da28d", "symbol_count": 10, "symbols": [{"doc": "A Flask app with the auth gate enabled, token 'sek'.\n\nEach test gets a fresh app so the module-level _GATE slot is clean.", "kind": "function", "line": 22, "name": "app_with_gate", "signature": "def app_with_gate(monkeypatch)"}, {"kind": "function", "line": 41, "name": "test_gate_auto_generates_token_when_unset", "signature": "def test_gate_auto_generates_token_when_unset(monkeypatch)"}, {"kind": "function", "line": 53, "name": "test_gate_on_rejects_anonymous", "signature": "def test_gate_on_rejects_anonymous(app_with_gate)"}, {"kind": "function", "line": 61, "name": "test_gate_on_accepts_bearer_header", "signature": "def test_gate_on_accepts_bearer_header(app_with_gate)"}, {"kind": "function", "line": 68, "name": "test_gate_on_accepts_alt_header", "signature": "def test_gate_on_accepts_alt_header(app_with_gate)"}, {"kind": "function", "line": 74, "name": "test_gate_on_accepts_cookie", "signature": "def test_gate_on_accepts_cookie(app_with_gate)"}, {"kind": "function", "line": 81, "name": "test_gate_on_rejects_wrong_token", "signature": "def test_gate_on_rejects_wrong_token(app_with_gate)"}, {"kind": "function", "line": 87, "name": "test_gate_on_auto_generated_token_in_meta", "signature": "def test_gate_on_auto_generated_token_in_meta(monkeypatch)"}, {"kind": "function", "line": 95, "name": "test_gate_on_exposes_token_for_index_meta", "signature": "def test_gate_on_exposes_token_for_index_meta()"}, {"kind": "function", "line": 34, "name": "private", "signature": "def private()"}]}, {"doc": "M4 case_crypto BDD red.", "id": "tests/test_case_crypto.py", "kind": "module", "label": "test_case_crypto.py", "language": "py", "sha256": "b3179f67d78c494d", "symbol_count": 5, "symbols": [{"kind": "function", "line": 5, "name": "_fresh_store", "signature": "def _fresh_store(tmp_path, monkeypatch, key)"}, {"kind": "function", "line": 22, "name": "test_disabled_stores_plaintext", "signature": "def test_disabled_stores_plaintext(tmp_path, monkeypatch)"}, {"kind": "function", "line": 33, "name": "test_enabled_roundtrip", "signature": "def test_enabled_roundtrip(tmp_path, monkeypatch)"}, {"kind": "function", "line": 58, "name": "test_bad_key_falls_back", "signature": "def test_bad_key_falls_back(tmp_path, monkeypatch)"}, {"kind": "function", "line": 69, "name": "test_mixed_rows_and_tamper", "signature": "def test_mixed_rows_and_tamper(tmp_path, monkeypatch)"}]}, {"doc": "M6 RED tests: central config for orchestrator + tool_install.", "id": "tests/test_central_config.py", "kind": "module", "label": "test_central_config.py", "language": "py", "sha256": "29994cfcc832cc21", "symbol_count": 3, "symbols": [{"kind": "function", "line": 7, "name": "test_config_defaults", "signature": "def test_config_defaults()"}, {"kind": "function", "line": 20, "name": "test_run_defaults_track_config", "signature": "def test_run_defaults_track_config()"}, {"kind": "function", "line": 29, "name": "test_tool_install_survives_malformed_env", "signature": "def test_tool_install_survives_malformed_env(monkeypatch)"}]}, {"doc": "ATDD + BDD tests for estorides_core.change_detection.  Implements the Given-When-Then contracts declared in ``spec/change_detection.md`` (module 2c). Property-based invariants live in ``tests/properties/test_change_detection_properties.py``.  Run from the project root::  .venv/bin/pytest tests/test_change_detection.py -v", "id": "tests/test_change_detection.py", "kind": "module", "label": "test_change_detection.py", "language": "py", "sha256": "dd7d08604a6b5bd3", "symbol_count": 43, "symbols": [{"kind": "function", "line": 28, "name": "_entity", "signature": "def _entity(eid, etype, value)"}, {"doc": "S1 del spec.", "kind": "class", "line": 56, "name": "TestNewEntity", "signature": "class TestNewEntity"}, {"doc": "S2 del spec.", "kind": "class", "line": 99, "name": "TestPropertyChanged", "signature": "class TestPropertyChanged"}, {"doc": "S3 del spec.", "kind": "class", "line": 125, "name": "TestFirstRunBeforeIsNone", "signature": "class TestFirstRunBeforeIsNone"}, {"doc": "S4 del spec.", "kind": "class", "line": 147, "name": "TestAfterIsNone", "signature": "class TestAfterIsNone"}, {"doc": "S5 del spec.", "kind": "class", "line": 166, "name": "TestDisappearedWithGrace", "signature": "class TestDisappearedWithGrace"}, {"doc": "S6 del spec.", "kind": "class", "line": 199, "name": "TestSourceAdded", "signature": "class TestSourceAdded"}, {"doc": "S7 del spec.", "kind": "class", "line": 226, "name": "TestMinReliabilityFiltersSources", "signature": "class TestMinReliabilityFiltersSources"}, {"doc": "S8 del spec.", "kind": "class", "line": 255, "name": "TestMaxChangesBounds", "signature": "class TestMaxChangesBounds"}, {"doc": "S9 del spec.", "kind": "class", "line": 281, "name": "TestProgrammerErrorRaises", "signature": "class TestProgrammerErrorRaises"}, {"doc": "S10 del spec.", "kind": "class", "line": 310, "name": "TestHostilePropertyKey", "signature": "class TestHostilePropertyKey"}, {"doc": "S11 del spec.", "kind": "class", "line": 346, "name": "TestDeterminism", "signature": "class TestDeterminism"}, {"doc": "S13 del spec: una source que antes veía la entity ya no la ve.", "kind": "class", "line": 386, "name": "TestSourceRemoved", "signature": "class TestSourceRemoved"}, {"doc": "S14 del spec: edges salientes que aparecen/desaparecen.", "kind": "class", "line": 436, "name": "TestEdgeChanges", "signature": "class TestEdgeChanges"}, {"doc": "S15 del spec: |confidence_after - confidence_before| > 0.20.", "kind": "class", "line": 488, "name": "TestConfidenceShifted", "signature": "class TestConfidenceShifted"}, {"doc": "S12 del spec: smoke test del dataclass.", "kind": "class", "line": 528, "name": "TestBoundedSmoke", "signature": "class TestBoundedSmoke"}, {"kind": "method", "line": 59, "name": "test_one_new_entity_emits_one_new_change", "signature": "def test_one_new_entity_emits_one_new_change(self)"}, {"kind": "method", "line": 84, "name": "test_new_change_score_in_high_band", "signature": "def test_new_change_score_in_high_band(self)"}, {"kind": "method", "line": 102, "name": "test_property_change_emits_one_change", "signature": "def test_property_change_emits_one_change(self)"}, {"kind": "method", "line": 128, "name": "test_before_none_reports_all_as_new", "signature": "def test_before_none_reports_all_as_new(self)"}, {"kind": "method", "line": 150, "name": "test_after_none_returns_empty_report", "signature": "def test_after_none_returns_empty_report(self)"}, {"kind": "method", "line": 169, "name": "test_disappeared_within_grace_is_ignored", "signature": "def test_disappeared_within_grace_is_ignored(self)"}, {"kind": "method", "line": 181, "name": "test_disappeared_outside_grace_emits_change", "signature": "def test_disappeared_outside_grace_emits_change(self)"}, {"kind": "method", "line": 202, "name": "test_new_source_on_existing_entity_emits_source_added", "signature": "def test_new_source_on_existing_entity_emits_source_added(self)"}, {"kind": "method", "line": 229, "name": "test_min_reliability_e_excludes_f_source", "signature": "def test_min_reliability_e_excludes_f_source(self)"}, {"kind": "method", "line": 258, "name": "test_max_changes_caps_output", "signature": "def test_max_changes_caps_output(self)"}, {"kind": "method", "line": 284, "name": "test_entity_id_empty_raises", "signature": "def test_entity_id_empty_raises(self)"}, {"kind": "method", "line": 288, "name": "test_entity_type_empty_raises", "signature": "def test_entity_type_empty_raises(self)"}, {"kind": "method", "line": 292, "name": "test_entity_value_empty_raises", "signature": "def test_entity_value_empty_raises(self)"}, {"kind": "method", "line": 296, "name": "test_min_change_score_out_of_range_raises", "signature": "def test_min_change_score_out_of_range_raises(self)"}, {"kind": "method", "line": 302, "name": "test_max_changes_too_small_raises", "signature": "def test_max_changes_too_small_raises(self)"}, {"kind": "method", "line": 322, "name": "test_hostile_key_does_not_crash", "signature": "def test_hostile_key_does_not_crash(self, hostile_key)"}, {"kind": "method", "line": 349, "name": "test_same_input_same_ids_and_scores", "signature": "def test_same_input_same_ids_and_scores(self)"}, {"kind": "method", "line": 366, "name": "test_input_order_does_not_affect_output", "signature": "def test_input_order_does_not_affect_output(self)"}, {"kind": "method", "line": 389, "name": "test_source_removed_emits_change", "signature": "def test_source_removed_emits_change(self)"}, {"kind": "method", "line": 410, "name": "test_source_removed_filtered_by_min_score", "signature": "def test_source_removed_filtered_by_min_score(self)"}, {"kind": "method", "line": 439, "name": "test_edge_added_emits_change", "signature": "def test_edge_added_emits_change(self)"}, {"kind": "method", "line": 462, "name": "test_edge_removed_emits_change", "signature": "def test_edge_removed_emits_change(self)"}, {"kind": "method", "line": 491, "name": "test_large_confidence_shift_emits_change", "signature": "def test_large_confidence_shift_emits_change(self)"}, {"kind": "method", "line": 509, "name": "test_small_confidence_shift_ignored", "signature": "def test_small_confidence_shift_ignored(self)"}, {"kind": "method", "line": 531, "name": "test_change_is_frozen", "signature": "def test_change_is_frozen(self)"}, {"kind": "method", "line": 541, "name": "test_diff_is_frozen", "signature": "def test_diff_is_frozen(self)"}, {"kind": "method", "line": 546, "name": "test_change_report_is_frozen", "signature": "def test_change_report_is_frozen(self)"}]}, {"doc": "BDD / regression tests for the `estorides watch add` CLI path.  Context (boy-scout fix): `cmd_watch_add` used to crash with `AttributeError` (the `watch add` subparser never declared `--proxy` / `--passive-only`) and then `NameError` (`SCHEDULER_ENABLED` undefined). These tests pin the contract so the path can never silently rot again.  - CW1: parser accepts the OPSEC flags on `watch add` - CW2: `cmd_watch_add` persists the watch and starts the scheduler - CW3: a malformed interval < 15 is rejected before any side effect - CW4: the scheduler runner is wired exactly once (idempotent)", "id": "tests/test_cli_watch.py", "kind": "module", "label": "test_cli_watch.py", "language": "py", "sha256": "c818003d2701dc7d", "symbol_count": 16, "symbols": [{"doc": "Minimal stand-in for WatchScheduler: records calls, no threads.", "kind": "class", "line": 24, "name": "_FakeScheduler", "signature": "class _FakeScheduler"}, {"doc": "Isolate the CLI from the real singletons.", "kind": "method", "line": 47, "name": "cli_env", "signature": "def cli_env(monkeypatch, tmp_path)"}, {"kind": "class", "line": 59, "name": "TestCW1Parser", "signature": "class TestCW1Parser"}, {"kind": "class", "line": 69, "name": "TestCW2Command", "signature": "class TestCW2Command"}, {"kind": "class", "line": 92, "name": "TestCW3Validation", "signature": "class TestCW3Validation"}, {"kind": "class", "line": 102, "name": "TestCW4RunnerFactory", "signature": "class TestCW4RunnerFactory"}, {"kind": "method", "line": 27, "name": "__init__", "signature": "def __init__(self)"}, {"kind": "method", "line": 34, "name": "has_runner", "signature": "def has_runner(self)"}, {"kind": "method", "line": 37, "name": "set_runner", "signature": "def set_runner(self, runner)"}, {"kind": "method", "line": 41, "name": "start", "signature": "def start(self)"}, {"kind": "method", "line": 60, "name": "test_watch_add_accepts_opsec_flags", "signature": "def test_watch_add_accepts_opsec_flags(self)"}, {"kind": "method", "line": 70, "name": "test_watch_add_persists_and_starts", "signature": "def test_watch_add_persists_and_starts(self, cli_env)"}, {"kind": "method", "line": 82, "name": "test_watch_add_wires_runner_once", "signature": "def test_watch_add_wires_runner_once(self, cli_env)"}, {"kind": "method", "line": 93, "name": "test_interval_below_minimum_rejected", "signature": "def test_interval_below_minimum_rejected(self, cli_env)"}, {"kind": "method", "line": 103, "name": "test_runner_factory_returns_awaitable_callable", "signature": "def test_runner_factory_returns_awaitable_callable(self, cli_env)"}, {"kind": "method", "line": 109, "name": "test_scheduler_has_runner_property", "signature": "def test_scheduler_has_runner_property(self, tmp_path)"}]}, {"doc": "ATDD + BDD tests for estorides_core.cloud_asset_discovery.  Implements the Given-When-Then contracts declared in ``spec/cloud_asset_discovery.md``.", "id": "tests/test_cloud_asset_discovery.py", "kind": "module", "label": "test_cloud_asset_discovery.py", "language": "py", "sha256": "243a75319c101ac6", "symbol_count": 18, "symbols": [{"kind": "function", "line": 16, "name": "_make_asset", "signature": "def _make_asset(provider, url, accessible, listing)"}, {"kind": "class", "line": 27, "name": "TestPublicS3Bucket", "signature": "class TestPublicS3Bucket"}, {"kind": "class", "line": 40, "name": "TestBucketWithListing", "signature": "class TestBucketWithListing"}, {"kind": "class", "line": 58, "name": "TestNoAssets", "signature": "class TestNoAssets"}, {"kind": "class", "line": 68, "name": "TestReadOnlyMethods", "signature": "class TestReadOnlyMethods"}, {"kind": "class", "line": 77, "name": "TestCloudFrontDetection", "signature": "class TestCloudFrontDetection"}, {"kind": "class", "line": 90, "name": "TestFirebaseDiscovery", "signature": "class TestFirebaseDiscovery"}, {"kind": "class", "line": 103, "name": "TestBucketNamePermutations", "signature": "class TestBucketNamePermutations"}, {"kind": "class", "line": 127, "name": "TestRateLimiting", "signature": "class TestRateLimiting"}, {"kind": "method", "line": 28, "name": "test_accessible_bucket_returned", "signature": "def test_accessible_bucket_returned(self)"}, {"kind": "method", "line": 41, "name": "test_listing_bucket_has_files", "signature": "def test_listing_bucket_has_files(self)"}, {"kind": "method", "line": 59, "name": "test_empty_when_no_cloud_assets", "signature": "def test_empty_when_no_cloud_assets(self)"}, {"kind": "method", "line": 69, "name": "test_assess_bucket_uses_get_only", "signature": "def test_assess_bucket_uses_get_only(self)"}, {"kind": "method", "line": 78, "name": "test_cloudfront_cname_detected", "signature": "def test_cloudfront_cname_detected(self)"}, {"kind": "method", "line": 91, "name": "test_firebase_accessible", "signature": "def test_firebase_accessible(self)"}, {"kind": "method", "line": 104, "name": "test_generates_common_permutations", "signature": "def test_generates_common_permutations(self)"}, {"kind": "method", "line": 114, "name": "test_strips_tld_for_permutations", "signature": "def test_strips_tld_for_permutations(self)"}, {"kind": "method", "line": 128, "name": "test_rate_limit_constant_defined", "signature": "def test_rate_limit_constant_defined(self)"}]}, {"doc": "ATDD + BDD tests for estorides_core.code_exposure.  Implements the Given-When-Then contracts declared in ``spec/code_exposure.md``.", "id": "tests/test_code_exposure.py", "kind": "module", "label": "test_code_exposure.py", "language": "py", "sha256": "fe763daa3dc7dd79", "symbol_count": 21, "symbols": [{"kind": "class", "line": 17, "name": "TestAwsKeyDetection", "signature": "class TestAwsKeyDetection"}, {"kind": "class", "line": 38, "name": "TestNoFindings", "signature": "class TestNoFindings"}, {"kind": "class", "line": 47, "name": "TestRateLimited", "signature": "class TestRateLimited"}, {"kind": "class", "line": 54, "name": "TestInternalUrlDetection", "signature": "class TestInternalUrlDetection"}, {"kind": "class", "line": 62, "name": "TestEnvFileDetection", "signature": "class TestEnvFileDetection"}, {"kind": "class", "line": 70, "name": "TestPlaceholderCredentials", "signature": "class TestPlaceholderCredentials"}, {"kind": "class", "line": 81, "name": "TestMultiPlatform", "signature": "class TestMultiPlatform"}, {"kind": "class", "line": 95, "name": "TestSnippetBounded", "signature": "class TestSnippetBounded"}, {"kind": "class", "line": 103, "name": "TestClassifyFindingTypes", "signature": "class TestClassifyFindingTypes"}, {"kind": "method", "line": 18, "name": "test_detects_aws_key_as_critical", "signature": "def test_detects_aws_key_as_critical(self)"}, {"kind": "method", "line": 32, "name": "test_validates_aws_key_format", "signature": "def test_validates_aws_key_format(self)"}, {"kind": "method", "line": 39, "name": "test_empty_when_no_repos", "signature": "def test_empty_when_no_repos(self)"}, {"kind": "method", "line": 48, "name": "test_returns_partial_on_rate_limit", "signature": "def test_returns_partial_on_rate_limit(self)"}, {"kind": "method", "line": 55, "name": "test_internal_url_is_high_severity", "signature": "def test_internal_url_is_high_severity(self)"}, {"kind": "method", "line": 63, "name": "test_env_file_is_critical_config", "signature": "def test_env_file_is_critical_config(self)"}, {"kind": "method", "line": 71, "name": "test_placeholder_marked_info", "signature": "def test_placeholder_marked_info(self)"}, {"kind": "method", "line": 75, "name": "test_example_key_marked_info", "signature": "def test_example_key_marked_info(self)"}, {"kind": "method", "line": 82, "name": "test_aggregates_multi_source", "signature": "def test_aggregates_multi_source(self)"}, {"kind": "method", "line": 96, "name": "test_snippet_under_200_chars", "signature": "def test_snippet_under_200_chars(self)"}, {"kind": "method", "line": 104, "name": "test_ssh_key_detection", "signature": "def test_ssh_key_detection(self)"}, {"kind": "method", "line": 109, "name": "test_api_key_detection", "signature": "def test_api_key_detection(self)"}]}, {"doc": "BDD / regression tests for fault-tolerant configuration parsing.  Doctrine (`config.py` docstring): a malformed env var must fall back to the default and log, never crash the process at import time. These tests pin that contract for every typed reader and for the import-time constant surface.  - CE1: typed readers parse valid values - CE2: a malformed value falls back to the default - CE3: a malformed env var cannot crash `import estorides_core.config` - CE4: CSV lists are whitespace-stripped", "id": "tests/test_config_env.py", "kind": "module", "label": "test_config_env.py", "language": "py", "sha256": "ae9a6bb2c9ec4fb1", "symbol_count": 13, "symbols": [{"kind": "function", "line": 23, "name": "_run_with_env", "signature": "def _run_with_env(env_overrides, code)"}, {"kind": "class", "line": 33, "name": "TestCE1TypedReaders", "signature": "class TestCE1TypedReaders"}, {"kind": "class", "line": 47, "name": "TestCE2MalformedFallsBack", "signature": "class TestCE2MalformedFallsBack"}, {"kind": "class", "line": 76, "name": "TestCE3ImportNeverCrashes", "signature": "class TestCE3ImportNeverCrashes"}, {"kind": "class", "line": 92, "name": "TestCE4CsvStripped", "signature": "class TestCE4CsvStripped"}, {"kind": "method", "line": 34, "name": "test_env_int_parses", "signature": "def test_env_int_parses(self, monkeypatch)"}, {"kind": "method", "line": 40, "name": "test_env_float_parses", "signature": "def test_env_float_parses(self, monkeypatch)"}, {"kind": "method", "line": 48, "name": "test_env_int_garbage", "signature": "def test_env_int_garbage(self, monkeypatch)"}, {"kind": "method", "line": 54, "name": "test_env_float_garbage", "signature": "def test_env_float_garbage(self, monkeypatch)"}, {"kind": "method", "line": 60, "name": "test_env_bool_unknown_uses_default", "signature": "def test_env_bool_unknown_uses_default(self, monkeypatch)"}, {"kind": "method", "line": 67, "name": "test_env_bool_tokens", "signature": "def test_env_bool_tokens(self, monkeypatch)"}, {"kind": "method", "line": 77, "name": "test_malformed_numeric_env_does_not_break_import", "signature": "def test_malformed_numeric_env_does_not_break_import(self)"}, {"kind": "method", "line": 93, "name": "test_exact_dedup_keys_are_stripped", "signature": "def test_exact_dedup_keys_are_stripped(self)"}]}, {"doc": "CSP-safe styles: regression tests for the inline-style bug introduced by the CSP tightening in commit 45c3af5.  The contract:  * The HTML template must not contain any `style=\"...\"` attributes. Browsers block those under the locked-down `style-src` policy. * The JS bundle must not emit `style=\"...\"` strings in any template literal — those would be re-injected into the DOM as inline styles and blocked the same way. * Dynamic per-cluster / per-kind colouring must go through the CSSOM (`el.style.background = cs`), which CSP does *not* restrict. * The CSP itself must not have been relaxed back to `'unsafe-inline'`. * The eight offscreen elements must use the HTML5 `hidden` attribute (which the browser's user-agent stylesheet turns into `display:none`), not a literal `style=\"display:none\"`.  These tests parse the files on disk; they do not require a running app. A complementary Flask-level test renders `index.html` through a test client to confirm the same holds in the served output.", "id": "tests/test_csp_safe_styles.py", "kind": "module", "label": "test_csp_safe_styles.py", "language": "py", "sha256": "74e8616babbe8601", "symbol_count": 11, "symbols": [{"doc": "Replace `{{ ... }}` and `{% ... %}` with empty so the file is grep-able.\n\nWe only care whether the *static markup* contains `style=\"...\"`. A\n`{{ estorides_auth_token or '' }}` cannot produce a `style=\"` because\nthe auth token is a short opaque string and Jinja escapes it\n(autoescape=on by default for .html). Even if the token were\nmalicious, a separate test (S7) covers the injection vector.", "kind": "function", "line": 41, "name": "_strip_template_jinja", "signature": "def _strip_template_jinja(template_text)"}, {"doc": "Return the *template-literal contents* of the JS file as a single string.\n\nWe can't simply grep `style=\"` because of false positives in comments\nand regular strings (e.g. error messages). The CSP issue is\nspecifically about values written into the DOM via `innerHTML`\ncontaining `style=\"`. The only way to do that in this codebase is\nthrough a template literal that gets assigned to `innerHTML`,\n`outerHTML`, or inserted via `insertAdjacentHTML`.\n\nStrategy: strip line (`// …`) and block (`/* … */`) comments\nfirst so backticks inside comments don't confuse the template\nliteral scanner. Then extract every backtick-delimited template\nliteral and return them joined.", "kind": "function", "line": 55, "name": "_strip_js_comments_and_strings_outside_templates", "signature": "def _strip_js_comments_and_strings_outside_templates(js_text)"}, {"doc": "S1 — `style=\"...\"` must not appear anywhere in the rendered template.\n\nBrowsers reject inline style attributes under the locked-down\n`style-src` policy. The fix is to use CSS classes (or `hidden`) —\nnot to relax the policy.", "kind": "function", "line": 90, "name": "test_index_html_has_no_style_attribute", "signature": "def test_index_html_has_no_style_attribute()"}, {"doc": "S2 — `style=\"...\"` must not appear in any template literal in the JS.\n\nJS uses `innerHTML = `…`` to inject HTML. The literal text must\nnot contain `style=\"…\"` because the browser will then try to\napply that style attribute and CSP will block it.", "kind": "function", "line": 111, "name": "test_estorides_js_has_no_style_in_template_literals", "signature": "def test_estorides_js_has_no_style_in_template_literals()"}, {"doc": "S3 — Each offscreen element must have the HTML5 `hidden` attribute.\n\nThe browser's user-agent stylesheet already turns `[hidden]` into\n`display: none`, so we don't need a literal `style=\"display:none\"`.", "kind": "function", "line": 145, "name": "test_offscreen_element_uses_hidden_attribute", "signature": "def test_offscreen_element_uses_hidden_attribute(element_id)"}, {"doc": "The CSS file must define the new classes the refactor relies on.", "kind": "function", "line": 189, "name": "test_css_has_required_class", "signature": "def test_css_has_required_class(selector)"}, {"doc": "S5 — The locked-down CSP must stay locked down.\n\nThe whole point of the refactor is that we don't have to relax\n`style-src` to make the UI work. If this test ever fails, the\nprevious fix was reverted to `'unsafe-inline'` — a regression.", "kind": "function", "line": 201, "name": "test_csp_policy_does_not_relax_for_unsafe_inline", "signature": "def test_csp_policy_does_not_relax_for_unsafe_inline()"}, {"doc": "S6 — The default CSP string is byte-identical to the pre-refactor value.\n\nThe fix is in the frontend, not in the policy. If this test fails,\nthe policy was changed instead of refactored.", "kind": "function", "line": 225, "name": "test_csp_policy_is_unchanged_after_refactor", "signature": "def test_csp_policy_is_unchanged_after_refactor()"}, {"doc": "S4 — The bridge-tooltip chip must set background via CSSOM.\n\nWe assert that the JS code *does* set `chip.style.background = cs`\n(or `span.style.backgroundColor = cs`) and that the HTML string\nfor the chip is built without a `style=\"background:…\"` attribute.", "kind": "function", "line": 252, "name": "test_dynamic_cluster_color_uses_cssom_assignment", "signature": "def test_dynamic_cluster_color_uses_cssom_assignment()"}, {"doc": "S4 (kind) — `colorForKind(e.kind)` must reach CSSOM, not innerHTML.", "kind": "function", "line": 272, "name": "test_dynamic_kind_color_uses_cssom_assignment", "signature": "def test_dynamic_kind_color_uses_cssom_assignment()"}, {"doc": "End-to-end: render `index.html` and assert no inline styles leak.", "kind": "function", "line": 289, "name": "test_rendered_template_has_no_style_attribute_and_uses_hidden", "signature": "def test_rendered_template_has_no_style_attribute_and_uses_hidden()"}]}, {"doc": "Encrypted export: plaintext must be removed after encryption.  Issue #8 reported that `export_stix_encrypted` and `export_misp_encrypted` left a plaintext STIX/MISP bundle on disk in `reports/`. The fix is to delete the plaintext in a `finally` block so it always goes away, even when encryption fails. The `age` binary is mocked in these tests so the suite is hermetic.", "id": "tests/test_encrypted_export.py", "kind": "module", "label": "test_encrypted_export.py", "language": "py", "sha256": "3d97bf5ae19787de", "symbol_count": 9, "symbols": [{"kind": "class", "line": 21, "name": "_FakeCompleted", "signature": "class _FakeCompleted"}, {"kind": "method", "line": 27, "name": "_kg_with_one_node", "signature": "def _kg_with_one_node()"}, {"doc": "Pretend `age` is on PATH and that `age -e -r ...` produced ciphertext.", "kind": "method", "line": 33, "name": "_patch_age_ok", "signature": "def _patch_age_ok()"}, {"kind": "method", "line": 43, "name": "test_stix_encrypted_removes_plaintext", "signature": "def test_stix_encrypted_removes_plaintext(tmp_path)"}, {"kind": "method", "line": 52, "name": "test_misp_encrypted_removes_plaintext", "signature": "def test_misp_encrypted_removes_plaintext(tmp_path)"}, {"doc": "Even when age fails, the plaintext must be removed.", "kind": "method", "line": 61, "name": "test_stix_encrypted_removes_plaintext_on_failure", "signature": "def test_stix_encrypted_removes_plaintext_on_failure(tmp_path)"}, {"kind": "method", "line": 22, "name": "__init__", "signature": "def __init__(self, rc, stderr)"}, {"kind": "method", "line": 35, "name": "_run", "signature": "def _run(cmd, stdin, stdout, stderr, check)"}, {"kind": "method", "line": 66, "name": "_run_fail", "signature": "def _run_fail(cmd, stdin, stdout, stderr, check)"}]}, {"doc": "BDD / regression tests for entity extraction and fuzzy merge.  - EE1: `types=[]` extracts nothing (and `types=None` extracts everything) - EE2: fuzzy merge picks the most-observed spelling as canonical", "id": "tests/test_entity_extraction.py", "kind": "module", "label": "test_entity_extraction.py", "language": "py", "sha256": "7b186bdfec91446b", "symbol_count": 7, "symbols": [{"kind": "class", "line": 12, "name": "TestEE1TypesFilter", "signature": "class TestEE1TypesFilter"}, {"kind": "class", "line": 28, "name": "TestEE2FuzzyCanonical", "signature": "class TestEE2FuzzyCanonical"}, {"kind": "method", "line": 15, "name": "test_none_means_all", "signature": "def test_none_means_all(self)"}, {"kind": "method", "line": 20, "name": "test_empty_list_means_nothing", "signature": "def test_empty_list_means_nothing(self)"}, {"kind": "method", "line": 23, "name": "test_subset_restricts", "signature": "def test_subset_restricts(self)"}, {"doc": "Given two spellings of the same domain, the widely-seen one is\ncanonical even when it is not the shortest.", "kind": "method", "line": 29, "name": "test_most_observed_spelling_wins", "signature": "def test_most_observed_spelling_wins(self)"}, {"kind": "method", "line": 41, "name": "test_merge_unions_sources", "signature": "def test_merge_unions_sources(self)"}]}, {"doc": "BDD tests for estorides_core.entity_resolution.  These tests implement the Given-When-Then contracts declared in spec/entity_resolution.md. They must fail against the unwritten implementation (red step) and pass after the green step.", "id": "tests/test_entity_resolution.py", "kind": "module", "label": "test_entity_resolution.py", "language": "py", "sha256": "cd3bf5d0d22bd4d7", "symbol_count": 68, "symbols": [{"kind": "function", "line": 28, "name": "_ent", "signature": "def _ent(etype, value, source, confidence)"}, {"kind": "function", "line": 32, "name": "_by_value", "signature": "def _by_value(result, value)"}, {"doc": "Cyrillic, Greek, Arabic, diacritic folding.", "kind": "class", "line": 42, "name": "TestTransliteration", "signature": "class TestTransliteration"}, {"doc": "Jaro-Winkler similarity invariants.", "kind": "class", "line": 68, "name": "TestJaroWinkler", "signature": "class TestJaroWinkler"}, {"doc": "Type-aware normalisation.", "kind": "class", "line": 89, "name": "TestNormalization", "signature": "class TestNormalization"}, {"doc": "Deterministic content-addressed ids.", "kind": "class", "line": 133, "name": "TestCanonicalId", "signature": "class TestCanonicalId"}, {"doc": "ER1: latin + cyrillic + comma-variant → one identity.", "kind": "class", "line": 155, "name": "TestCrossScriptPersonFusion", "signature": "class TestCrossScriptPersonFusion"}, {"doc": "ER2: EvilCorp.com and evilcorp.com → exact merge.", "kind": "class", "line": 194, "name": "TestDomainCaseVariantMerge", "signature": "class TestDomainCaseVariantMerge"}, {"doc": "ER3: evilcorp.com vs evil-corp.com → SAME_AS link, not merged.", "kind": "class", "line": 221, "name": "TestLookAlikeDomainsSurfaceAsLink", "signature": "class TestLookAlikeDomainsSurfaceAsLink"}, {"doc": "ER4: md5 differing by one char → separate entities.", "kind": "class", "line": 246, "name": "TestDeterministicTypeNoFuzzyMatch", "signature": "class TestDeterministicTypeNoFuzzyMatch"}, {"doc": "ER5: same IP from two sources → merged.", "kind": "class", "line": 272, "name": "TestIdenticalIpsMerge", "signature": "class TestIdenticalIpsMerge"}, {"doc": "ER6: 8.8.8.8 and 8.8.4.4 → separate.", "kind": "class", "line": 289, "name": "TestNearIpsNeverFuse", "signature": "class TestNearIpsNeverFuse"}, {"doc": "ER7: Evil Corp LLC + Evil Corp → merged.", "kind": "class", "line": 309, "name": "TestOrgSuffixFolding", "signature": "class TestOrgSuffixFolding"}, {"doc": "ER8: Putin and Medvedev → separate identities.", "kind": "class", "line": 326, "name": "TestDistinctPersonsStaySeparate", "signature": "class TestDistinctPersonsStaySeparate"}, {"doc": "ER9: to_dict and to_entity preserve data.", "kind": "class", "line": 347, "name": "TestCanonicalEntityRoundtrip", "signature": "class TestCanonicalEntityRoundtrip"}, {"doc": "ER10: empty list → empty result.", "kind": "class", "line": 382, "name": "TestEmptyInput", "signature": "class TestEmptyInput"}, {"doc": "ER11: same input → same id.", "kind": "class", "line": 394, "name": "TestCanonicalIdDeterministic", "signature": "class TestCanonicalIdDeterministic"}, {"doc": "ER12: different input → different id.", "kind": "class", "line": 406, "name": "TestDifferentInputDifferentId", "signature": "class TestDifferentInputDifferentId"}, {"doc": "Additional edge cases beyond the spec scenarios.", "kind": "class", "line": 418, "name": "TestEdgeCases", "signature": "class TestEdgeCases"}, {"doc": "Canonical id stays stable across runs via persistent store.", "kind": "class", "line": 448, "name": "TestCrossRunStability", "signature": "class TestCrossRunStability"}, {"kind": "method", "line": 45, "name": "test_cyrillic_to_latin", "signature": "def test_cyrillic_to_latin(self)"}, {"kind": "method", "line": 48, "name": "test_greek_accented_to_latin", "signature": "def test_greek_accented_to_latin(self)"}, {"kind": "method", "line": 51, "name": "test_diacritic_fold", "signature": "def test_diacritic_fold(self)"}, {"kind": "method", "line": 54, "name": "test_consonant_skeleton_arabic_matches_latin", "signature": "def test_consonant_skeleton_arabic_matches_latin(self)"}, {"kind": "method", "line": 57, "name": "test_consonant_skeleton_gemination", "signature": "def test_consonant_skeleton_gemination(self)"}, {"kind": "method", "line": 60, "name": "test_distinct_names_have_distinct_skeletons", "signature": "def test_distinct_names_have_distinct_skeletons(self)"}, {"kind": "method", "line": 63, "name": "test_non_latin_detector", "signature": "def test_non_latin_detector(self)"}, {"kind": "method", "line": 71, "name": "test_identical_strings_score_one", "signature": "def test_identical_strings_score_one(self)"}, {"kind": "method", "line": 74, "name": "test_empty_pair_scores_zero", "signature": "def test_empty_pair_scores_zero(self)"}, {"kind": "method", "line": 77, "name": "test_classic_jaro_winkler_bound", "signature": "def test_classic_jaro_winkler_bound(self)"}, {"kind": "method", "line": 81, "name": "test_dissimilar_strings_score_low", "signature": "def test_dissimilar_strings_score_low(self)"}, {"kind": "method", "line": 84, "name": "test_scores_stay_in_unit_interval", "signature": "def test_scores_stay_in_unit_interval(self)"}, {"kind": "method", "line": 92, "name": "test_ipv4_normalised", "signature": "def test_ipv4_normalised(self)"}, {"kind": "method", "line": 95, "name": "test_ipv6_compressed", "signature": "def test_ipv6_compressed(self)"}, {"kind": "method", "line": 101, "name": "test_hash_lowered", "signature": "def test_hash_lowered(self)"}, {"kind": "method", "line": 107, "name": "test_cve_uppered", "signature": "def test_cve_uppered(self)"}, {"kind": "method", "line": 110, "name": "test_domain_strips_scheme_www_path", "signature": "def test_domain_strips_scheme_www_path(self)"}, {"kind": "method", "line": 116, "name": "test_person_order_independent", "signature": "def test_person_order_independent(self)"}, {"kind": "method", "line": 121, "name": "test_org_suffix_stripped", "signature": "def test_org_suffix_stripped(self)"}, {"kind": "method", "line": 126, "name": "test_asn_normalised", "signature": "def test_asn_normalised(self)"}, {"kind": "method", "line": 129, "name": "test_email_lowered", "signature": "def test_email_lowered(self)"}, {"kind": "method", "line": 136, "name": "test_deterministic", "signature": "def test_deterministic(self)"}, {"kind": "method", "line": 141, "name": "test_different_values_different_ids", "signature": "def test_different_values_different_ids(self)"}, {"kind": "method", "line": 146, "name": "test_id_format", "signature": "def test_id_format(self)"}, {"kind": "method", "line": 158, "name": "test_three_spellings_fuse", "signature": "def test_three_spellings_fuse(self)"}, {"kind": "method", "line": 169, "name": "test_fused_identity_carries_all_sources", "signature": "def test_fused_identity_carries_all_sources(self)"}, {"kind": "method", "line": 180, "name": "test_cross_script_flagged_in_attributes", "signature": "def test_cross_script_flagged_in_attributes(self)"}, {"kind": "method", "line": 197, "name": "test_domain_case_variants_merge", "signature": "def test_domain_case_variants_merge(self)"}, {"kind": "method", "line": 207, "name": "test_domain_merge_is_exact", "signature": "def test_domain_merge_is_exact(self)"}, {"kind": "method", "line": 224, "name": "test_look_alike_domains_stay_separate", "signature": "def test_look_alike_domains_stay_separate(self)"}, {"kind": "method", "line": 233, "name": "test_look_alike_domains_produce_same_as_link", "signature": "def test_look_alike_domains_produce_same_as_link(self)"}, {"kind": "method", "line": 249, "name": "test_deterministic_near_miss_never_matches", "signature": "def test_deterministic_near_miss_never_matches(self)"}, {"kind": "method", "line": 259, "name": "test_score_pair_deterministic_mismatch", "signature": "def test_score_pair_deterministic_mismatch(self)"}, {"kind": "method", "line": 275, "name": "test_identical_ips_merge", "signature": "def test_identical_ips_merge(self)"}, {"kind": "method", "line": 292, "name": "test_near_ips_stay_separate", "signature": "def test_near_ips_stay_separate(self)"}, {"kind": "method", "line": 312, "name": "test_org_suffix_variants_merge", "signature": "def test_org_suffix_variants_merge(self)"}, {"kind": "method", "line": 329, "name": "test_distinct_persons_not_absorbed", "signature": "def test_distinct_persons_not_absorbed(self)"}, {"kind": "method", "line": 350, "name": "test_to_dict_serialises", "signature": "def test_to_dict_serialises(self)"}, {"kind": "method", "line": 361, "name": "test_to_entity_projects_legacy", "signature": "def test_to_entity_projects_legacy(self)"}, {"kind": "method", "line": 373, "name": "test_resolution_result_has_one_entity", "signature": "def test_resolution_result_has_one_entity(self)"}, {"kind": "method", "line": 385, "name": "test_empty_input_returns_empty", "signature": "def test_empty_input_returns_empty(self)"}, {"kind": "method", "line": 397, "name": "test_same_normalised_same_id", "signature": "def test_same_normalised_same_id(self)"}, {"kind": "method", "line": 409, "name": "test_different_normalised_different_id", "signature": "def test_different_normalised_different_id(self)"}, {"kind": "method", "line": 421, "name": "test_blank_value_does_not_crash", "signature": "def test_blank_value_does_not_crash(self)"}, {"kind": "method", "line": 425, "name": "test_whitespace_only_handled", "signature": "def test_whitespace_only_handled(self)"}, {"kind": "method", "line": 429, "name": "test_single_entity_produces_one_canonical", "signature": "def test_single_entity_produces_one_canonical(self)"}, {"kind": "method", "line": 434, "name": "test_confidence_boosted_by_multiple_sources", "signature": "def test_confidence_boosted_by_multiple_sources(self)"}, {"kind": "method", "line": 451, "name": "test_cross_run_id_stability", "signature": "def test_cross_run_id_stability(self)"}]}, {"doc": "BDD tests for the shared env readers (spec/envutil.md).", "id": "tests/test_envutil.py", "kind": "module", "label": "test_envutil.py", "language": "py", "sha256": "28b07749e1ef7c94", "symbol_count": 12, "symbols": [{"kind": "class", "line": 7, "name": "TestEnvInt", "signature": "class TestEnvInt"}, {"kind": "class", "line": 20, "name": "TestEnvFloat", "signature": "class TestEnvFloat"}, {"kind": "class", "line": 28, "name": "TestEnvBool", "signature": "class TestEnvBool"}, {"kind": "class", "line": 40, "name": "TestEnvCsv", "signature": "class TestEnvCsv"}, {"kind": "method", "line": 8, "name": "test_parse", "signature": "def test_parse(self, monkeypatch)"}, {"kind": "method", "line": 12, "name": "test_malformed", "signature": "def test_malformed(self, monkeypatch)"}, {"kind": "method", "line": 16, "name": "test_absent", "signature": "def test_absent(self)"}, {"kind": "method", "line": 21, "name": "test_parse_and_malformed", "signature": "def test_parse_and_malformed(self, monkeypatch)"}, {"kind": "method", "line": 29, "name": "test_tokens", "signature": "def test_tokens(self, monkeypatch)"}, {"kind": "method", "line": 35, "name": "test_unknown_uses_default", "signature": "def test_unknown_uses_default(self, monkeypatch)"}, {"kind": "method", "line": 41, "name": "test_strip_and_filter", "signature": "def test_strip_and_filter(self, monkeypatch)"}, {"kind": "method", "line": 45, "name": "test_default", "signature": "def test_default(self)"}]}, {"doc": "M2a event_bus BDD red.", "id": "tests/test_event_bus.py", "kind": "module", "label": "test_event_bus.py", "language": "py", "sha256": "2f54d08f6a2a7c15", "symbol_count": 6, "symbols": [{"kind": "function", "line": 7, "name": "test_subscribe_publish_order_and_count", "signature": "def test_subscribe_publish_order_and_count()"}, {"kind": "function", "line": 19, "name": "test_raising_handler_does_not_block_others", "signature": "def test_raising_handler_does_not_block_others()"}, {"kind": "function", "line": 34, "name": "test_payload_isolation_between_handlers", "signature": "def test_payload_isolation_between_handlers()"}, {"kind": "function", "line": 45, "name": "test_invalid_inputs_raise", "signature": "def test_invalid_inputs_raise()"}, {"kind": "function", "line": 57, "name": "test_singleton_and_unsubscribe", "signature": "def test_singleton_and_unsubscribe()"}, {"kind": "function", "line": 22, "name": "bad", "signature": "def bad(p)"}]}, {"doc": "ATDD + BDD tests for estorides_core.fusion_analytics.  Implements the Given-When-Then contracts from spec/fusion_analytics.md (module 2e). Must FAIL at the red step (implementation doesn't exist yet) and PASS after the green step.  Run from project root: .venv/bin/pytest tests/test_fusion_analytics.py -v", "id": "tests/test_fusion_analytics.py", "kind": "module", "label": "test_fusion_analytics.py", "language": "py", "sha256": "632ecdb4fe9ad67d", "symbol_count": 36, "symbols": [{"kind": "function", "line": 27, "name": "store_and_analytics", "signature": "def store_and_analytics(tmp_path)"}, {"kind": "function", "line": 42, "name": "_populate_evilcorp", "signature": "def _populate_evilcorp(store)"}, {"kind": "function", "line": 57, "name": "_register_source", "signature": "def _register_source(store, name)"}, {"kind": "class", "line": 64, "name": "TestEntityTimeline", "signature": "class TestEntityTimeline"}, {"kind": "class", "line": 87, "name": "TestEntitySummary", "signature": "class TestEntitySummary"}, {"kind": "class", "line": 109, "name": "TestEntityTimelineNonexistent", "signature": "class TestEntityTimelineNonexistent"}, {"kind": "class", "line": 118, "name": "TestSourceStats", "signature": "class TestSourceStats"}, {"kind": "class", "line": 155, "name": "TestMultiSourceConsensus", "signature": "class TestMultiSourceConsensus"}, {"kind": "class", "line": 176, "name": "TestCorroboratedProperties", "signature": "class TestCorroboratedProperties"}, {"kind": "class", "line": 195, "name": "TestEntitySearch", "signature": "class TestEntitySearch"}, {"kind": "class", "line": 230, "name": "TestTopChanged", "signature": "class TestTopChanged"}, {"kind": "class", "line": 250, "name": "TestSourceCorroborationMatrix", "signature": "class TestSourceCorroborationMatrix"}, {"kind": "class", "line": 268, "name": "TestWithNoneStore", "signature": "class TestWithNoneStore"}, {"kind": "class", "line": 281, "name": "TestBoundaryConditions", "signature": "class TestBoundaryConditions"}, {"kind": "method", "line": 65, "name": "test_returns_full_timeline", "signature": "def test_returns_full_timeline(self, store_and_analytics)"}, {"kind": "method", "line": 79, "name": "test_nonexistent_eid_returns_none", "signature": "def test_nonexistent_eid_returns_none(self, store_and_analytics)"}, {"kind": "method", "line": 88, "name": "test_returns_summary_stats", "signature": "def test_returns_summary_stats(self, store_and_analytics)"}, {"kind": "method", "line": 101, "name": "test_nonexistent_eid_returns_none", "signature": "def test_nonexistent_eid_returns_none(self, store_and_analytics)"}, {"kind": "method", "line": 110, "name": "test_returns_none", "signature": "def test_returns_none(self, store_and_analytics)"}, {"kind": "method", "line": 119, "name": "test_returns_source_metrics", "signature": "def test_returns_source_metrics(self, store_and_analytics)"}, {"kind": "method", "line": 135, "name": "test_nonexistent_source_returns_none", "signature": "def test_nonexistent_source_returns_none(self, store_and_analytics)"}, {"kind": "method", "line": 139, "name": "test_success_rate_correct", "signature": "def test_success_rate_correct(self, store_and_analytics)"}, {"kind": "method", "line": 156, "name": "test_consensus_picks_majority_value", "signature": "def test_consensus_picks_majority_value(self, store_and_analytics)"}, {"kind": "method", "line": 165, "name": "test_nonexistent_key_returns_empty", "signature": "def test_nonexistent_key_returns_empty(self, store_and_analytics)"}, {"kind": "method", "line": 177, "name": "test_filters_by_min_sources", "signature": "def test_filters_by_min_sources(self, store_and_analytics)"}, {"kind": "method", "line": 185, "name": "test_min_sources_one_returns_all", "signature": "def test_min_sources_one_returns_all(self, store_and_analytics)"}, {"kind": "method", "line": 196, "name": "test_search_by_term", "signature": "def test_search_by_term(self, store_and_analytics)"}, {"kind": "method", "line": 208, "name": "test_search_no_results", "signature": "def test_search_no_results(self, store_and_analytics)"}, {"kind": "method", "line": 213, "name": "test_search_filter_by_type", "signature": "def test_search_filter_by_type(self, store_and_analytics)"}, {"kind": "method", "line": 219, "name": "test_search_with_confidence_and_source_filters", "signature": "def test_search_with_confidence_and_source_filters(self, store_and_analytics)"}, {"kind": "method", "line": 231, "name": "test_returns_recently_active_entities", "signature": "def test_returns_recently_active_entities(self, store_and_analytics)"}, {"kind": "method", "line": 241, "name": "test_empty_window_returns_empty", "signature": "def test_empty_window_returns_empty(self, store_and_analytics)"}, {"kind": "method", "line": 251, "name": "test_returns_pairs_with_shared_counts", "signature": "def test_returns_pairs_with_shared_counts(self, store_and_analytics)"}, {"kind": "method", "line": 269, "name": "test_all_methods_return_empty", "signature": "def test_all_methods_return_empty(self)"}, {"kind": "method", "line": 282, "name": "test_min_sources_zero_treated_as_one", "signature": "def test_min_sources_zero_treated_as_one(self, store_and_analytics)"}, {"kind": "method", "line": 288, "name": "test_negative_days_treated_as_one", "signature": "def test_negative_days_treated_as_one(self, store_and_analytics)"}]}, {"doc": "graph_bundle: pestana Bundles circular 2D + esferica 3D estilo ReadMenator.  Escenarios del contrato `spec/graph_bundle.md`: S1 happy path (dos comunidades), S2 edge (vacio + unassigned), S3 error (scaffolding/rotas fuera, no-dict falla cerrado), S4 seguridad (truncado + JSON-safe), S5 determinismo + settings.", "id": "tests/test_graph_bundle.py", "kind": "module", "label": "test_graph_bundle.py", "language": "py", "sha256": "cef1a93f300b0431", "symbol_count": 30, "symbols": [{"kind": "function", "line": 26, "name": "_force", "signature": "def _force()"}, {"doc": "S1 — 3 entidades en 2 grupos, 2 aristas, angulos y esferas validos.", "kind": "function", "line": 62, "name": "test_s1_two_communities_circle_and_sphere", "signature": "def test_s1_two_communities_circle_and_sphere()"}, {"doc": "S2 — vacio sin raise; sin comunidad forma grupo trailing.", "kind": "function", "line": 83, "name": "test_s2_empty_and_unassigned", "signature": "def test_s2_empty_and_unassigned()"}, {"doc": "S3 — member_of/layered_as/huerfanas/self-loop fuera; no-dict TypeError.", "kind": "function", "line": 104, "name": "test_s3_scaffolding_dropped_and_bad_node_raises", "signature": "def test_s3_scaffolding_dropped_and_bad_node_raises()"}, {"doc": "S4 — label 5KB truncado a <=512; bytes hace TypeError; dumps OK.", "kind": "function", "line": 124, "name": "test_s4_hostile_labels_truncated_and_bytes_raise", "signature": "def test_s4_hostile_labels_truncated_and_bytes_raise()"}, {"doc": "S5 — dos pasadas identicas; p/u unitarios; settings con defaults.", "kind": "function", "line": 149, "name": "test_s5_deterministic_and_settings_match_readmenator", "signature": "def test_s5_deterministic_and_settings_match_readmenator()"}, {"doc": "S7 — curvas con extremos cerca de las hojas; beta=0 pega al acorde.", "kind": "function", "line": 169, "name": "test_s7_circle_curves_endpoints_and_beta", "signature": "def test_s7_circle_curves_endpoints_and_beta()"}, {"doc": "S7 — caps exactos, hubs dentro, hojas unitarias, fibonacci borde.", "kind": "function", "line": 205, "name": "test_s7_sphere_caps_hubs_and_fibonacci", "signature": "def test_s7_sphere_caps_hubs_and_fibonacci()"}, {"doc": "S7 — _unit(0)=polo, _round digitos, _opt/_req ramas.", "kind": "function", "line": 228, "name": "test_s7_helpers_unit", "signature": "def test_s7_helpers_unit()"}, {"doc": "S8 — dict exacto con env limpio; override y malformado no tumban.", "kind": "function", "line": 253, "name": "test_s8_settings_full_defaults_and_env", "signature": "def test_s8_settings_full_defaults_and_env(monkeypatch)"}, {"doc": "S8 — grupos/nodos/edges byte-exactos sobre el fixture S1.", "kind": "function", "line": 288, "name": "test_s8_exact_payload_shape", "signature": "def test_s8_exact_payload_shape()"}, {"doc": "S8 — None/no-str van a defaults; tiers y communities raras fuera.", "kind": "function", "line": 308, "name": "test_s8_lenient_fields_and_ignored_nodes", "signature": "def test_s8_lenient_fields_and_ignored_nodes()"}, {"doc": "S9 — los TypeError llevan el mensaje exacto (mata mutantes de texto).", "kind": "function", "line": 351, "name": "test_s9_typeerror_messages_asserted", "signature": "def test_s9_typeerror_messages_asserted()"}, {"doc": "S9 — entidad pelada usa todos los defaults; sin id el id es ''.", "kind": "function", "line": 374, "name": "test_s9_bare_entity_defaults_and_idless", "signature": "def test_s9_bare_entity_defaults_and_idless()"}, {"doc": "S9 — sin color manda family; comunidad alta antes que unassigned.", "kind": "function", "line": 397, "name": "test_s9_family_fallback_color_and_order", "signature": "def test_s9_family_fallback_color_and_order()"}, {"doc": "S9 — _unit en normas limite (mata <= frente a <).", "kind": "function", "line": 418, "name": "test_s9_unit_edge_norms", "signature": "def test_s9_unit_edge_norms()"}, {"doc": "S9 — lattice exacto (angulo dorado y reparto vertical pineados).", "kind": "function", "line": 427, "name": "test_s9_fibonacci_exact", "signature": "def test_s9_fibonacci_exact()"}, {"doc": "S9 — caps exactos; llamada sin radius usa 1.0.", "kind": "function", "line": 443, "name": "test_s9_sphere_caps_exact_and_defaults", "signature": "def test_s9_sphere_caps_exact_and_defaults()"}, {"doc": "S9 — checksum de curva con defaults (beta/samples/inner_ratio pineados).", "kind": "function", "line": 457, "name": "test_s9_default_layout_checksum_and_length", "signature": "def test_s9_default_layout_checksum_and_length()"}, {"doc": "S9 — cada ESTORIDES_BUNDLE_* llega a su clave (nombres pineados).", "kind": "function", "line": 474, "name": "test_s9_all_env_vars_propagate", "signature": "def test_s9_all_env_vars_propagate(monkeypatch)"}, {"doc": "S10 — payload S1 byte-exacto (cada clave/valor pineado).", "kind": "function", "line": 507, "name": "test_s10_full_literal_output", "signature": "def test_s10_full_literal_output()"}, {"doc": "S10 — label '' usa el id; findings no-cero viaja.", "kind": "function", "line": 539, "name": "test_s10_empty_label_falls_back_to_id_and_findings", "signature": "def test_s10_empty_label_falls_back_to_id_and_findings()"}, {"doc": "S10 — 'community:9:9' se descarta; sin label el grupo es 'cluster N'.", "kind": "function", "line": 553, "name": "test_s10_odd_community_ids", "signature": "def test_s10_odd_community_ids()"}, {"doc": "S10 — member_of entre entidades se filtra por tipo, no por extremos.", "kind": "function", "line": 580, "name": "test_s10_scaffold_between_entities_dropped", "signature": "def test_s10_scaffold_between_entities_dropped()"}, {"doc": "S10 — un id malo no aborta el resto (continue, no break).", "kind": "function", "line": 599, "name": "test_s10_community_colors_survive_bad_id", "signature": "def test_s10_community_colors_survive_bad_id()"}, {"doc": "S10 — GROUP_GAP/INNER_RATIO del env mandan (kwargs no opcionales).", "kind": "function", "line": 623, "name": "test_s10_env_divergence_changes_layout", "signature": "def test_s10_env_divergence_changes_layout(monkeypatch)"}, {"doc": "S10 — rama <3 puntos: interpolacion lineal exacta + longitudes.", "kind": "function", "line": 636, "name": "test_s10_bspline_short_branch_exact", "signature": "def test_s10_bspline_short_branch_exact()"}, {"doc": "S10 — beta>1 se clamp a 1; arista mala primera no aborta.", "kind": "function", "line": 651, "name": "test_s10_beta_clamp_and_skip_order", "signature": "def test_s10_beta_clamp_and_skip_order()"}, {"doc": "S10 — layouts vacios exactos; hojas absolutas (centro/radio pineados).", "kind": "function", "line": 668, "name": "test_s10_empty_layouts_full_and_absolute_leaves", "signature": "def test_s10_empty_layouts_full_and_absolute_leaves()"}, {"doc": "S10 — 3 grupos: reparto exacto; empate [1,4,1] corta en 1; eje con lattice sintetico.", "kind": "function", "line": 692, "name": "test_s10_three_group_caps_and_tie_and_axis", "signature": "def test_s10_three_group_caps_and_tie_and_axis()"}]}, {"doc": "graph_bundle S6: assets frontend (fuera del sandbox mutmut).  Este archivo NO entra en la seleccion de mutmut (solo mira tests/test_graph_bundle.py): el sandbox `mutants/` no copia templates/static y estos asserts fallarian alli por FileNotFound.", "id": "tests/test_graph_bundle_assets.py", "kind": "module", "label": "test_graph_bundle_assets.py", "language": "py", "sha256": "c36892d4e574030e", "symbol_count": 1, "symbols": [{"doc": "S6 — tab Bundles + JS sin innerHTML/eval + CSS con tokens.", "kind": "function", "line": 19, "name": "test_s6_frontend_assets_exist_and_are_csp_clean", "signature": "def test_s6_frontend_assets_exist_and_are_csp_clean()"}]}, {"doc": "graph_force3d: port del sistema de grafos ReadMenator + contexto IA.  Escenarios del contrato `spec/graph_force3d.md`: S1 happy path (payload RAW), S2 edge (vacio), S3 error/truncacion determinista con puentes primero, S4 seguridad (no JSON-safe -> TypeError), S5 seguridad (contexto IA acotado y sin inyeccion markdown), S6 settings y colores deterministas.", "id": "tests/test_graph_force3d.py", "kind": "module", "label": "test_graph_force3d.py", "language": "py", "sha256": "302ef1e4f3ac6f32", "symbol_count": 9, "symbols": [{"kind": "function", "line": 22, "name": "_nodes", "signature": "def _nodes()"}, {"kind": "function", "line": 36, "name": "_edges", "signature": "def _edges()"}, {"kind": "function", "line": 45, "name": "_clusters", "signature": "def _clusters()"}, {"doc": "S1 — 3 nodos + 2 edges producen entidades, comunidades y tiers.", "kind": "function", "line": 53, "name": "test_s1_build_force_payload_shape", "signature": "def test_s1_build_force_payload_shape()"}, {"doc": "S2 — payload vacio y contexto 'empty graph' sin excepciones.", "kind": "function", "line": 79, "name": "test_s2_empty_graph_no_raise", "signature": "def test_s2_empty_graph_no_raise()"}, {"doc": "S3 — dos pasadas identicas; el puente sobrevive al corte.", "kind": "function", "line": 90, "name": "test_s3_truncation_deterministic_bridge_first", "signature": "def test_s3_truncation_deterministic_bridge_first()"}, {"doc": "S4 — set/bytes en labels -> TypeError, nunca payload parcial.", "kind": "function", "line": 112, "name": "test_s4_non_json_safe_raises_typeerror", "signature": "def test_s4_non_json_safe_raises_typeerror()"}, {"doc": "S5 — budget duro, fences pares, marcador truncated.", "kind": "function", "line": 130, "name": "test_s5_ai_context_budget_and_no_markdown_injection", "signature": "def test_s5_ai_context_budget_and_no_markdown_injection()"}, {"doc": "S6 — mismo HSL siempre; settings con los 19 valores ReadMenator.", "kind": "function", "line": 149, "name": "test_s6_family_color_deterministic_and_settings_match_readmenator", "signature": "def test_s6_family_color_deterministic_and_settings_match_readmenator()"}]}, {"doc": "graph_rag_search: BM25 + PageRank/PPR + map-reduce global (contrato S1-S6).", "id": "tests/test_graph_rag_search.py", "kind": "module", "label": "test_graph_rag_search.py", "language": "py", "sha256": "f150b45168359482", "symbol_count": 11, "symbols": [{"kind": "function", "line": 18, "name": "_nodes", "signature": "def _nodes()"}, {"kind": "function", "line": 29, "name": "_edges", "signature": "def _edges()"}, {"kind": "function", "line": 36, "name": "_clusters", "signature": "def _clusters()"}, {"kind": "function", "line": 43, "name": "_searcher", "signature": "def _searcher()"}, {"doc": "S1 — query con match lexico va en local y la entidad lidera.", "kind": "function", "line": 48, "name": "test_s1_local_match_leads", "signature": "def test_s1_local_match_leads()"}, {"doc": "S2 — hints de overview eligen map-reduce con reportes.", "kind": "function", "line": 61, "name": "test_s2_global_hints_map_reduce", "signature": "def test_s2_global_hints_map_reduce()"}, {"doc": "S3 — indice vacio responde sin romper; el mensaje local es explicito.", "kind": "function", "line": 71, "name": "test_s3_empty_graph_no_raise", "signature": "def test_s3_empty_graph_no_raise()"}, {"doc": "S4 — set/bytes -> TypeError; fuzzinlinea acotado con fences pares.", "kind": "function", "line": 83, "name": "test_s4_hostile_fails_closed_and_bounded", "signature": "def test_s4_hostile_fails_closed_and_bounded()"}, {"doc": "S5 — PR suma 1, seed lidera PPR, todo byte-identico entre pasadas.", "kind": "function", "line": 108, "name": "test_s5_pagerank_sums_to_one_and_deterministic", "signature": "def test_s5_pagerank_sums_to_one_and_deterministic()"}, {"doc": "S6 — bloque de prompt desde nx.DiGraph, acotado; vacio -> ''.", "kind": "function", "line": 124, "name": "test_s6_context_block_from_digraph", "signature": "def test_s6_context_block_from_digraph()"}, {"doc": "S7 — `_graph_rag_block` lee el grafo y falla a '' sin grafo.\n\nEl ignore de ResourceWarning es deliberado: este test no abre DBs;\ncon `filterwarnings=error` el GC puede atribuirle conexiones sqlite\nsin cerrar dejadas por otros tests de la suite (víctima aleatoria).", "kind": "function", "line": 142, "name": "test_s7_web_helper_uses_graph_path_fail_soft", "signature": "def test_s7_web_helper_uses_graph_path_fail_soft(tmp_path, monkeypatch)"}]}, {"doc": "BDD tests for the v1.3 hardening surface, case diff and report.  Ported from the standalone ``_test_hardening.py`` validator into pytest so CI runs it. Offline.", "id": "tests/test_hardening.py", "kind": "module", "label": "test_hardening.py", "language": "py", "sha256": "8e25590e7f2ecdd8", "symbol_count": 17, "symbols": [{"kind": "function", "line": 23, "name": "_secured_app", "signature": "def _secured_app(cfg)"}, {"kind": "class", "line": 35, "name": "TestSecurityHeaders", "signature": "class TestSecurityHeaders"}, {"kind": "class", "line": 48, "name": "TestCors", "signature": "class TestCors"}, {"kind": "class", "line": 64, "name": "TestDebugKillswitch", "signature": "class TestDebugKillswitch"}, {"kind": "class", "line": 72, "name": "TestCaseDiff", "signature": "class TestCaseDiff"}, {"kind": "class", "line": 107, "name": "TestReport", "signature": "class TestReport"}, {"kind": "class", "line": 137, "name": "TestConsoleScript", "signature": "class TestConsoleScript"}, {"kind": "method", "line": 29, "name": "status", "signature": "def status()"}, {"kind": "method", "line": 36, "name": "test_headers_and_body_cap", "signature": "def test_headers_and_body_cap(self)"}, {"kind": "method", "line": 49, "name": "test_default_off", "signature": "def test_default_off(self)"}, {"kind": "method", "line": 54, "name": "test_allowlist", "signature": "def test_allowlist(self)"}, {"kind": "method", "line": 65, "name": "test_debug_raises", "signature": "def test_debug_raises(self)"}, {"kind": "method", "line": 73, "name": "test_diff_counts", "signature": "def test_diff_counts(self, tmp_path)"}, {"kind": "method", "line": 97, "name": "test_set_notes", "signature": "def test_set_notes(self, tmp_path)"}, {"kind": "method", "line": 108, "name": "test_renders_sections", "signature": "def test_renders_sections(self)"}, {"kind": "method", "line": 122, "name": "test_with_diff", "signature": "def test_with_diff(self)"}, {"kind": "method", "line": 138, "name": "test_help", "signature": "def test_help(self)"}]}, {"doc": "ATDD + BDD tests for estorides_core.hypothesis_engine.  Implements the Given-When-Then contracts declared in ``spec/hypothesis_engine.md`` (module 2b). Property-based invariants live in ``tests/properties/test_hypothesis_engine_properties.py``.  Run from the project root::  .venv/bin/pytest tests/test_hypothesis_engine.py -v", "id": "tests/test_hypothesis_engine.py", "kind": "module", "label": "test_hypothesis_engine.py", "language": "py", "sha256": "9df4da849d00c02f", "symbol_count": 35, "symbols": [{"doc": "Build a minimal observation dict that matches the orchestrator shape.", "kind": "function", "line": 23, "name": "_obs", "signature": "def _obs(source, parsed, raw)"}, {"doc": "S1 del spec.", "kind": "class", "line": 39, "name": "TestHappyPathDomainBelongsToActor", "signature": "class TestHappyPathDomainBelongsToActor"}, {"doc": "S2 del spec.", "kind": "class", "line": 113, "name": "TestEmptyInputProducesEmptyOutput", "signature": "class TestEmptyInputProducesEmptyOutput"}, {"kind": "method", "line": 127, "name": "generate_hypothences_safe", "signature": "def generate_hypothences_safe(observations, entities)"}, {"doc": "S3 del spec.", "kind": "class", "line": 138, "name": "TestMalformedObservationsAreSkipped", "signature": "class TestMalformedObservationsAreSkipped"}, {"doc": "S4 del spec.", "kind": "class", "line": 176, "name": "TestUnknownSourceFallsBackToReliabilityC", "signature": "class TestUnknownSourceFallsBackToReliabilityC"}, {"doc": "S5 del spec.", "kind": "class", "line": 199, "name": "TestMinScoreFiltersHypotheses", "signature": "class TestMinScoreFiltersHypotheses"}, {"doc": "S6 del spec.", "kind": "class", "line": 230, "name": "TestMaxHypothesesBounds", "signature": "class TestMaxHypothesesBounds"}, {"doc": "S7 del spec.", "kind": "class", "line": 253, "name": "TestProgrammerErrorRaisesValueOrTypeError", "signature": "class TestProgrammerErrorRaisesValueOrTypeError"}, {"doc": "S8 del spec.", "kind": "class", "line": 280, "name": "TestHostileObservationPayloadIsHandled", "signature": "class TestHostileObservationPayloadIsHandled"}, {"doc": "S9 del spec.", "kind": "class", "line": 309, "name": "TestDeterminism", "signature": "class TestDeterminism"}, {"doc": "S10 del spec: smoke test del dataclass.", "kind": "class", "line": 351, "name": "TestBoundedSmoke", "signature": "class TestBoundedSmoke"}, {"kind": "method", "line": 42, "name": "test_emits_domain_belongsto_actor_hypothesis", "signature": "def test_emits_domain_belongsto_actor_hypothesis(self)"}, {"kind": "method", "line": 61, "name": "test_score_in_high_band", "signature": "def test_score_in_high_band(self)"}, {"kind": "method", "line": 76, "name": "test_supporting_has_three_items", "signature": "def test_supporting_has_three_items(self)"}, {"kind": "method", "line": 95, "name": "test_sources_sorted_and_unique", "signature": "def test_sources_sorted_and_unique(self)"}, {"kind": "method", "line": 116, "name": "test_empty_observations_empty_entities", "signature": "def test_empty_observations_empty_entities(self)"}, {"kind": "method", "line": 119, "name": "test_empty_observations_only", "signature": "def test_empty_observations_only(self)"}, {"kind": "method", "line": 122, "name": "test_empty_entities_only", "signature": "def test_empty_entities_only(self)"}, {"kind": "method", "line": 141, "name": "test_observation_with_none_parsed_is_ignored", "signature": "def test_observation_with_none_parsed_is_ignored(self)"}, {"kind": "method", "line": 155, "name": "test_observation_without_source_is_ignored", "signature": "def test_observation_without_source_is_ignored(self)"}, {"kind": "method", "line": 179, "name": "test_unknown_source_uses_reliability_c", "signature": "def test_unknown_source_uses_reliability_c(self)"}, {"kind": "method", "line": 202, "name": "test_min_score_zero_returns_all", "signature": "def test_min_score_zero_returns_all(self)"}, {"kind": "method", "line": 214, "name": "test_min_score_one_filters_everything", "signature": "def test_min_score_one_filters_everything(self)"}, {"kind": "method", "line": 233, "name": "test_max_hypotheses_caps_output", "signature": "def test_max_hypotheses_caps_output(self)"}, {"kind": "method", "line": 256, "name": "test_observations_must_be_sequence", "signature": "def test_observations_must_be_sequence(self)"}, {"kind": "method", "line": 260, "name": "test_entities_must_be_sequence", "signature": "def test_entities_must_be_sequence(self)"}, {"kind": "method", "line": 264, "name": "test_min_score_out_of_range_raises", "signature": "def test_min_score_out_of_range_raises(self)"}, {"kind": "method", "line": 270, "name": "test_max_hypotheses_too_small_raises", "signature": "def test_max_hypotheses_too_small_raises(self)"}, {"kind": "method", "line": 292, "name": "test_hostile_value_is_truncated_or_skipped", "signature": "def test_hostile_value_is_truncated_or_skipped(self, hostile)"}, {"kind": "method", "line": 312, "name": "test_same_input_same_ids_and_scores", "signature": "def test_same_input_same_ids_and_scores(self)"}, {"kind": "method", "line": 333, "name": "test_input_order_does_not_affect_output", "signature": "def test_input_order_does_not_affect_output(self)"}, {"kind": "method", "line": 354, "name": "test_hypothesis_dataclass_is_frozen", "signature": "def test_hypothesis_dataclass_is_frozen(self)"}, {"kind": "method", "line": 370, "name": "test_evidence_dataclass_is_frozen", "signature": "def test_evidence_dataclass_is_frozen(self)"}, {"kind": "method", "line": 382, "name": "test_entity_ref_is_frozen", "signature": "def test_entity_ref_is_frozen(self)"}]}, {"doc": "BDD tests for the shared deterministic id helper (spec/ids.md).  - ID1: deterministic and content-addressed - ID2: length is honoured - ID3: different payloads do not collide in the 16-char space (smoke)", "id": "tests/test_ids.py", "kind": "module", "label": "test_ids.py", "language": "py", "sha256": "69d18ae51ce982a4", "symbol_count": 7, "symbols": [{"kind": "class", "line": 15, "name": "TestID1Determinism", "signature": "class TestID1Determinism"}, {"kind": "class", "line": 27, "name": "TestID2Length", "signature": "class TestID2Length"}, {"kind": "class", "line": 33, "name": "TestID3Distinct", "signature": "class TestID3Distinct"}, {"kind": "method", "line": 16, "name": "test_same_payload_same_id", "signature": "def test_same_payload_same_id(self)"}, {"kind": "method", "line": 19, "name": "test_matches_legacy_formula", "signature": "def test_matches_legacy_formula(self)"}, {"kind": "method", "line": 28, "name": "test_length_honoured", "signature": "def test_length_honoured(self)"}, {"kind": "method", "line": 34, "name": "test_distinct_payloads", "signature": "def test_distinct_payloads(self)"}]}, {"doc": "BoundedJobRegistry: size cap + TTL eviction (issues #14, #20, #50).", "id": "tests/test_job_registry.py", "kind": "module", "label": "test_job_registry.py", "language": "py", "sha256": "c27087441b584b0c", "symbol_count": 8, "symbols": [{"kind": "function", "line": 11, "name": "test_register_returns_value", "signature": "def test_register_returns_value()"}, {"kind": "function", "line": 17, "name": "test_size_cap_evicts_oldest", "signature": "def test_size_cap_evicts_oldest()"}, {"kind": "function", "line": 28, "name": "test_get_refreshes_lru_order", "signature": "def test_get_refreshes_lru_order()"}, {"kind": "function", "line": 39, "name": "test_ttl_eviction", "signature": "def test_ttl_eviction()"}, {"kind": "function", "line": 53, "name": "test_pop_removes_entry", "signature": "def test_pop_removes_entry()"}, {"kind": "function", "line": 61, "name": "test_keys_values_consistent", "signature": "def test_keys_values_consistent()"}, {"kind": "function", "line": 69, "name": "test_invalid_construction", "signature": "def test_invalid_construction()"}, {"doc": "Re-registering the same key keeps the size stable and LRU order intact.", "kind": "function", "line": 76, "name": "test_replacement_does_not_evict", "signature": "def test_replacement_does_not_evict()"}]}, {"doc": "M3 RED tests: keyless BGPView + CISA KEV sources and total parsers.", "id": "tests/test_keyless_sources.py", "kind": "module", "label": "test_keyless_sources.py", "language": "py", "sha256": "a5073c7d1ffa43c5", "symbol_count": 7, "symbols": [{"kind": "function", "line": 9, "name": "_registry", "signature": "def _registry()"}, {"kind": "function", "line": 17, "name": "test_bgpview_ip_source", "signature": "def test_bgpview_ip_source()"}, {"kind": "function", "line": 25, "name": "test_bgpview_asn_source", "signature": "def test_bgpview_asn_source()"}, {"kind": "function", "line": 32, "name": "test_cisa_kev_source", "signature": "def test_cisa_kev_source()"}, {"kind": "function", "line": 39, "name": "test_parse_bgpview_extracts", "signature": "def test_parse_bgpview_extracts()"}, {"kind": "function", "line": 53, "name": "test_parsers_total_on_hostile_input", "signature": "def test_parsers_total_on_hostile_input()"}, {"kind": "function", "line": 62, "name": "test_parse_cisa_kev_extracts", "signature": "def test_parse_cisa_kev_extracts()"}]}, {"doc": "BDD tests for lan_bundle (spec/lan_bundle.md). Written before code.", "id": "tests/test_lan_bundle.py", "kind": "module", "label": "test_lan_bundle.py", "language": "py", "sha256": "384bc717273b9e73", "symbol_count": 9, "symbols": [{"kind": "function", "line": 9, "name": "_devs", "signature": "def _devs()"}, {"kind": "function", "line": 28, "name": "test_s1_bundle_graph_and_lazyown", "signature": "def test_s1_bundle_graph_and_lazyown()"}, {"kind": "function", "line": 37, "name": "test_s2_empty", "signature": "def test_s2_empty()"}, {"kind": "function", "line": 43, "name": "test_s3_stem_traversal_sanitized", "signature": "def test_s3_stem_traversal_sanitized()"}, {"kind": "function", "line": 47, "name": "test_s4_stix_shapes", "signature": "def test_s4_stix_shapes()"}, {"kind": "function", "line": 53, "name": "test_s5_write_atomic", "signature": "def test_s5_write_atomic(tmp_path)"}, {"kind": "function", "line": 68, "name": "test_s6_error_dir", "signature": "def test_s6_error_dir(tmp_path)"}, {"kind": "function", "line": 75, "name": "test_s7_scope_tagged", "signature": "def test_s7_scope_tagged()"}, {"kind": "function", "line": 81, "name": "test_s8_deterministic", "signature": "def test_s8_deterministic()"}]}, {"doc": "BDD tests for lan_inventory (spec/lan_inventory.md). Written before code.", "id": "tests/test_lan_inventory.py", "kind": "module", "label": "test_lan_inventory.py", "language": "py", "sha256": "5effca6ba18c7dee", "symbol_count": 14, "symbols": [{"kind": "function", "line": 6, "name": "test_s1_arp_parses", "signature": "def test_s1_arp_parses()"}, {"kind": "function", "line": 13, "name": "test_s2_dhcp_parses_hostname", "signature": "def test_s2_dhcp_parses_hostname()"}, {"kind": "function", "line": 19, "name": "test_s3_merge_same_mac", "signature": "def test_s3_merge_same_mac()"}, {"kind": "function", "line": 33, "name": "test_s4_merge_no_mac_by_ip", "signature": "def test_s4_merge_no_mac_by_ip()"}, {"kind": "function", "line": 46, "name": "test_s5_garbage_empty", "signature": "def test_s5_garbage_empty()"}, {"kind": "function", "line": 51, "name": "test_s6_public_dropped", "signature": "def test_s6_public_dropped()"}, {"kind": "function", "line": 58, "name": "test_s7_mac_formats_equal", "signature": "def test_s7_mac_formats_equal()"}, {"kind": "function", "line": 64, "name": "test_s9_dead_entries_dropped", "signature": "def test_s9_dead_entries_dropped()"}, {"kind": "function", "line": 99, "name": "test_s10_nmap_xml", "signature": "def test_s10_nmap_xml()"}, {"kind": "function", "line": 107, "name": "test_s11_nmap_grepable", "signature": "def test_s11_nmap_grepable()"}, {"kind": "function", "line": 114, "name": "test_s12_dispatch_tags_source", "signature": "def test_s12_dispatch_tags_source()"}, {"kind": "function", "line": 125, "name": "test_s13_hostile_xml_rejected", "signature": "def test_s13_hostile_xml_rejected()"}, {"kind": "function", "line": 130, "name": "test_s10_cross_source_fold", "signature": "def test_s10_cross_source_fold()"}, {"kind": "function", "line": 144, "name": "test_s8_contact_map", "signature": "def test_s8_contact_map()"}]}, {"doc": "BDD tests for lan_policy (spec/lan_policy.md). Written before code.", "id": "tests/test_lan_policy.py", "kind": "module", "label": "test_lan_policy.py", "language": "py", "sha256": "e2a1d857fda5a3c8", "symbol_count": 8, "symbols": [{"kind": "function", "line": 6, "name": "test_s1_passive_allowed_with_consent", "signature": "def test_s1_passive_allowed_with_consent()"}, {"kind": "function", "line": 16, "name": "test_s2_active_allowed_on_triple_gate", "signature": "def test_s2_active_allowed_on_triple_gate(monkeypatch)"}, {"kind": "function", "line": 27, "name": "test_s3_checkbox_without_env_ceiling_denies_active", "signature": "def test_s3_checkbox_without_env_ceiling_denies_active(monkeypatch)"}, {"kind": "function", "line": 37, "name": "test_s4_valid_private_scope_echoes", "signature": "def test_s4_valid_private_scope_echoes()"}, {"kind": "function", "line": 49, "name": "test_s5_no_consent_blocks_even_passive", "signature": "def test_s5_no_consent_blocks_even_passive()"}, {"kind": "function", "line": 58, "name": "test_s6_public_scope_blocked", "signature": "def test_s6_public_scope_blocked()"}, {"kind": "function", "line": 70, "name": "test_s7_truthy_string_consent_blocked", "signature": "def test_s7_truthy_string_consent_blocked()"}, {"kind": "function", "line": 79, "name": "test_s8_unknown_contact_denied_and_capped", "signature": "def test_s8_unknown_contact_denied_and_capped()"}]}, {"doc": "BDD tests for lan_runner (spec/lan_runner.md). Written before code.", "id": "tests/test_lan_runner.py", "kind": "module", "label": "test_lan_runner.py", "language": "py", "sha256": "569ba04577e47253", "symbol_count": 14, "symbols": [{"kind": "function", "line": 10, "name": "test_s1_passive_injected", "signature": "def test_s1_passive_injected(tmp_path)"}, {"kind": "function", "line": 22, "name": "test_s2_no_consent_refused", "signature": "def test_s2_no_consent_refused(tmp_path)"}, {"kind": "function", "line": 32, "name": "test_s3_bad_scope_refused", "signature": "def test_s3_bad_scope_refused(tmp_path)"}, {"kind": "function", "line": 41, "name": "test_s4_degrade_active", "signature": "def test_s4_degrade_active(tmp_path, monkeypatch)"}, {"kind": "function", "line": 53, "name": "test_s5_active_injected", "signature": "def test_s5_active_injected(tmp_path, monkeypatch)"}, {"kind": "class", "line": 63, "name": "_FakeToolResult", "signature": "class _FakeToolResult"}, {"kind": "method", "line": 69, "name": "test_s9_tool_order", "signature": "def test_s9_tool_order()"}, {"kind": "method", "line": 102, "name": "test_s6_empty_readers", "signature": "def test_s6_empty_readers(tmp_path)"}, {"kind": "method", "line": 112, "name": "test_s7_scope_auto", "signature": "def test_s7_scope_auto(tmp_path)"}, {"kind": "method", "line": 121, "name": "test_s8_no_scope_found", "signature": "def test_s8_no_scope_found(tmp_path)"}, {"kind": "method", "line": 64, "name": "__init__", "signature": "def __init__(self, exit_code, stdout)"}, {"kind": "method", "line": 74, "name": "ok_nmap", "signature": "def ok_nmap(tool, args)"}, {"kind": "method", "line": 81, "name": "fail_then_arp", "signature": "def fail_then_arp(tool, args)"}, {"kind": "method", "line": 93, "name": "both_fail", "signature": "def both_fail(tool, args)"}]}, {"doc": "BDD tests for lan_scope (spec/lan_scope.md). Written before code.", "id": "tests/test_lan_scope.py", "kind": "module", "label": "test_lan_scope.py", "language": "py", "sha256": "c8315835f0ba118c", "symbol_count": 8, "symbols": [{"kind": "function", "line": 6, "name": "test_s1_v4_parses_to_network", "signature": "def test_s1_v4_parses_to_network()"}, {"kind": "function", "line": 13, "name": "test_s2_v6_ula_accepted", "signature": "def test_s2_v6_ula_accepted()"}, {"kind": "function", "line": 18, "name": "test_s3_mixed_keeps_private_only", "signature": "def test_s3_mixed_keeps_private_only()"}, {"kind": "function", "line": 29, "name": "test_s4_garbage_returns_none", "signature": "def test_s4_garbage_returns_none()"}, {"kind": "function", "line": 33, "name": "test_s5_public_returns_none", "signature": "def test_s5_public_returns_none()"}, {"kind": "function", "line": 37, "name": "test_s6_wide_scope_rejected", "signature": "def test_s6_wide_scope_rejected()"}, {"kind": "function", "line": 41, "name": "test_s7_empty_default_none", "signature": "def test_s7_empty_default_none()"}, {"kind": "function", "line": 45, "name": "test_s8_reader_fail_soft", "signature": "def test_s8_reader_fail_soft(monkeypatch)"}]}, {"doc": "BDD tests for lan_web (spec/lan_web.md). Written before code.", "id": "tests/test_lan_web.py", "kind": "module", "label": "test_lan_web.py", "language": "py", "sha256": "b606b9121ec3a521", "symbol_count": 12, "symbols": [{"kind": "function", "line": 6, "name": "test_s1_passive_permitted", "signature": "def test_s1_passive_permitted()"}, {"kind": "function", "line": 12, "name": "test_s2_active_permitted", "signature": "def test_s2_active_permitted(monkeypatch)"}, {"kind": "function", "line": 21, "name": "test_s3_checkbox_without_ceiling_degrades", "signature": "def test_s3_checkbox_without_ceiling_degrades(monkeypatch)"}, {"kind": "function", "line": 32, "name": "test_s3b_pure_passive_has_no_notice", "signature": "def test_s3b_pure_passive_has_no_notice()"}, {"kind": "function", "line": 37, "name": "test_s4_no_consent_refused", "signature": "def test_s4_no_consent_refused()"}, {"kind": "function", "line": 43, "name": "test_s5_bad_scope_400", "signature": "def test_s5_bad_scope_400()"}, {"kind": "function", "line": 49, "name": "test_s6_string_consent_refused", "signature": "def test_s6_string_consent_refused()"}, {"kind": "function", "line": 54, "name": "test_s7_body_not_dict", "signature": "def test_s7_body_not_dict()"}, {"kind": "function", "line": 59, "name": "test_s9_lan_request_detection", "signature": "def test_s9_lan_request_detection()"}, {"kind": "function", "line": 66, "name": "test_s10_scope_payload", "signature": "def test_s10_scope_payload()"}, {"kind": "function", "line": 74, "name": "test_s11_active_intent_permitted", "signature": "def test_s11_active_intent_permitted(monkeypatch)"}, {"kind": "function", "line": 84, "name": "test_s8_scope_echo_normalized", "signature": "def test_s8_scope_echo_normalized()"}]}, {"doc": "BDD tests for spec/map_basemap.md (CARTO basemap, no direct OSM tiles).", "id": "tests/test_map_basemap.py", "kind": "module", "label": "test_map_basemap.py", "language": "py", "sha256": "cbe13ad940bcfe6a", "symbol_count": 5, "symbols": [{"kind": "function", "line": 10, "name": "_js", "signature": "def _js()"}, {"kind": "function", "line": 14, "name": "test_no_direct_osm_tiles_S1", "signature": "def test_no_direct_osm_tiles_S1()"}, {"kind": "function", "line": 22, "name": "test_esri_dark_provider_S2", "signature": "def test_esri_dark_provider_S2()"}, {"kind": "function", "line": 35, "name": "test_attribution_S3", "signature": "def test_attribution_S3()"}, {"kind": "function", "line": 41, "name": "test_csp_allows_tile_host_S5", "signature": "def test_csp_allows_tile_host_S5()"}]}, {"doc": "BDD tests for Monitoring & Advanced Recon modules.  Covers: - M1: Create a watch target - M2: Watch runs and completes - M3: Alert sent on completion - M4: Disable a watch - M5: Delete a watch - M9: Webhook alert channel - Advanced recon source YAML validation", "id": "tests/test_monitoring.py", "kind": "module", "label": "test_monitoring.py", "language": "py", "sha256": "103d5713cc51b785", "symbol_count": 35, "symbols": [{"doc": "Create a temporary WatchStore for testing, auto-closes on teardown.", "kind": "function", "line": 32, "name": "tmp_store", "signature": "def tmp_store()"}, {"kind": "function", "line": 42, "name": "sample_watch", "signature": "def sample_watch()"}, {"doc": "M1: Watch target creation.", "kind": "class", "line": 58, "name": "TestM1CreateWatch", "signature": "class TestM1CreateWatch"}, {"doc": "M2: Watch execution and completion.", "kind": "class", "line": 86, "name": "TestM2WatchRun", "signature": "class TestM2WatchRun"}, {"doc": "M4: Disabling a watch prevents it from running.", "kind": "class", "line": 115, "name": "TestM4DisableWatch", "signature": "class TestM4DisableWatch"}, {"doc": "M5: Deleting a watch removes it from the store.", "kind": "class", "line": 142, "name": "TestM5DeleteWatch", "signature": "class TestM5DeleteWatch"}, {"doc": "Watch run history recording.", "kind": "class", "line": 164, "name": "TestWatchHistory", "signature": "class TestWatchHistory"}, {"doc": "M9: Webhook alert channel sends correctly structured payload.", "kind": "class", "line": 194, "name": "TestM9WebhookAlert", "signature": "class TestM9WebhookAlert"}, {"doc": "Verify that the new recon source YAMLs are valid and loadable.", "kind": "class", "line": 211, "name": "TestAdvancedReconSources", "signature": "class TestAdvancedReconSources"}, {"doc": "Alert channel configuration checks.", "kind": "class", "line": 260, "name": "TestAlerterChannels", "signature": "class TestAlerterChannels"}, {"doc": "WatchTarget creation and serialisation.", "kind": "class", "line": 292, "name": "TestWatchTargetDataclass", "signature": "class TestWatchTargetDataclass"}, {"doc": "Verify that new sources are properly counted.", "kind": "class", "line": 324, "name": "TestSourceCount", "signature": "class TestSourceCount"}, {"doc": "Given a valid query, a watch is created with status pending.", "kind": "method", "line": 61, "name": "test_create_watch", "signature": "def test_create_watch(self, tmp_store)"}, {"doc": "Given a watch, next_run_at is in the future.", "kind": "method", "line": 69, "name": "test_watch_next_run_in_future", "signature": "def test_watch_next_run_in_future(self, sample_watch)"}, {"doc": "Given a persisted watch, it appears in the watch list.", "kind": "method", "line": 73, "name": "test_watch_appears_in_list", "signature": "def test_watch_appears_in_list(self, tmp_store, sample_watch)"}, {"doc": "Given enabled watches past due, only those are returned.", "kind": "method", "line": 89, "name": "test_due_watches_returns_enabled", "signature": "def test_due_watches_returns_enabled(self, tmp_store)"}, {"doc": "Given a watch with interval 15, due returns it at the right time.", "kind": "method", "line": 101, "name": "test_interval_honored", "signature": "def test_interval_honored(self, tmp_store)"}, {"doc": "Given a disabled watch, it still exists in the store.", "kind": "method", "line": 118, "name": "test_disable_does_not_delete", "signature": "def test_disable_does_not_delete(self, tmp_store, sample_watch)"}, {"doc": "Given a disabled watch past due, it is not returned as due.", "kind": "method", "line": 127, "name": "test_disabled_not_due", "signature": "def test_disabled_not_due(self, tmp_store)"}, {"doc": "Given a deleted watch, it no longer appears.", "kind": "method", "line": 145, "name": "test_delete_removes_watch", "signature": "def test_delete_removes_watch(self, tmp_store, sample_watch)"}, {"doc": "Given a deleted watch, the list no longer contains it.", "kind": "method", "line": 151, "name": "test_delete_removes_from_list", "signature": "def test_delete_removes_from_list(self, tmp_store, sample_watch)"}, {"doc": "Given a watch, recording a run returns a history id.", "kind": "method", "line": 167, "name": "test_record_run_start", "signature": "def test_record_run_start(self, tmp_store, sample_watch)"}, {"doc": "Given a completed run, history shows it.", "kind": "method", "line": 173, "name": "test_history_appears", "signature": "def test_history_appears(self, tmp_store, sample_watch)"}, {"doc": "Given a watch with no runs, history is empty.", "kind": "method", "line": 183, "name": "test_history_empty_for_new_watch", "signature": "def test_history_empty_for_new_watch(self, tmp_store, sample_watch)"}, {"doc": "Given a webhook alert, the payload has the right structure.", "kind": "method", "line": 197, "name": "test_webhook_builds_correct_payload", "signature": "def test_webhook_builds_correct_payload(self)"}, {"doc": "Given a source YAML file, it loads without errors.", "kind": "method", "line": 220, "name": "test_source_yaml_loads", "signature": "def test_source_yaml_loads(self, source_name)"}, {"doc": "Given a source YAML, it declares the correct key_env.", "kind": "method", "line": 235, "name": "test_source_requires_key", "signature": "def test_source_requires_key(self, source_name, key_env)"}, {"doc": "Given the new sources, all are contact: none (passive).", "kind": "method", "line": 244, "name": "test_all_new_sources_passive", "signature": "def test_all_new_sources_passive(self)"}, {"doc": "Given the dispatcher, available_channels returns all channel types.", "kind": "method", "line": 263, "name": "test_available_channels_returns_all", "signature": "def test_available_channels_returns_all(self)"}, {"doc": "Given available channels, each has its env_var listed.", "kind": "method", "line": 274, "name": "test_channel_env_vars_listed", "signature": "def test_channel_env_vars_listed(self)"}, {"doc": "Given an unknown channel name, send returns False.", "kind": "method", "line": 281, "name": "test_unknown_channel_returns_false", "signature": "def test_unknown_channel_returns_false(self)"}, {"doc": "Given a WatchTarget, to_dict and from_dict roundtrip.", "kind": "method", "line": 295, "name": "test_to_dict_roundtrip", "signature": "def test_to_dict_roundtrip(self)"}, {"doc": "Given a WatchTarget with no next_run_at, it defaults to future.", "kind": "method", "line": 308, "name": "test_default_next_run_set", "signature": "def test_default_next_run_set(self)"}, {"doc": "Given a WatchTarget with no channels, channels is empty.", "kind": "method", "line": 313, "name": "test_default_channels_empty", "signature": "def test_default_channels_empty(self)"}, {"doc": "Given the source directory, the new sources are loaded and counted.", "kind": "method", "line": 327, "name": "test_new_sources_loaded", "signature": "def test_new_sources_loaded(self)"}]}, {"doc": "Obs FTS BDD.", "id": "tests/test_obs_fts.py", "kind": "module", "label": "test_obs_fts.py", "language": "py", "sha256": "b4db0ff0b1ad77d8", "symbol_count": 3, "symbols": [{"kind": "function", "line": 5, "name": "_store", "signature": "def _store(tmp_path, monkeypatch)"}, {"kind": "function", "line": 16, "name": "test_fts_finds_rare_word", "signature": "def test_fts_finds_rare_word(tmp_path, monkeypatch)"}, {"kind": "function", "line": 33, "name": "test_fts_empty_and_injection_safe", "signature": "def test_fts_empty_and_injection_safe(tmp_path, monkeypatch)"}]}, {"doc": "estorides_core.observation_models — BDD/TDD contract tests.  Each test is the executable translation of a BDD scenario in `spec/observation_models.md` (O1-O8). The module under test does not exist yet; these tests must fail (import error) until the implementation lands.", "id": "tests/test_observation_models.py", "kind": "module", "label": "test_observation_models.py", "language": "py", "sha256": "e08ee8150b3118cd", "symbol_count": 28, "symbols": [{"kind": "function", "line": 25, "name": "_full_meta", "signature": "def _full_meta()"}, {"kind": "function", "line": 39, "name": "_full_obs", "signature": "def _full_obs()"}, {"kind": "function", "line": 57, "name": "test_o1_full_observation_validates", "signature": "def test_o1_full_observation_validates()"}, {"kind": "function", "line": 72, "name": "test_o2_error_observation_validates", "signature": "def test_o2_error_observation_validates()"}, {"kind": "function", "line": 85, "name": "test_o3_missing_required_field_fails", "signature": "def test_o3_missing_required_field_fails()"}, {"kind": "function", "line": 96, "name": "test_o4_unknown_meta_key_forbidden", "signature": "def test_o4_unknown_meta_key_forbidden()"}, {"kind": "function", "line": 106, "name": "test_o5_wrong_typed_field_fails", "signature": "def test_o5_wrong_typed_field_fails()"}, {"kind": "function", "line": 116, "name": "test_o6_oversized_url_truncated_not_failed", "signature": "def test_o6_oversized_url_truncated_not_failed()"}, {"kind": "function", "line": 124, "name": "test_o6_oversized_value_rejected", "signature": "def test_o6_oversized_value_rejected()"}, {"kind": "function", "line": 134, "name": "test_o6_error_message_does_not_embed_hostile_value", "signature": "def test_o6_error_message_does_not_embed_hostile_value()"}, {"kind": "function", "line": 146, "name": "test_o7_confidence_out_of_range_rejected", "signature": "def test_o7_confidence_out_of_range_rejected(confidence)"}, {"kind": "function", "line": 152, "name": "test_o7_confidence_at_bounds_accepted", "signature": "def test_o7_confidence_at_bounds_accepted()"}, {"kind": "function", "line": 163, "name": "test_o8_run_result_aggregates", "signature": "def test_o8_run_result_aggregates()"}, {"kind": "function", "line": 182, "name": "test_o8_run_result_with_error_surfaces_error", "signature": "def test_o8_run_result_with_error_surfaces_error()"}, {"kind": "function", "line": 198, "name": "test_security_non_json_safe_value_rejected", "signature": "def test_security_non_json_safe_value_rejected(field)"}, {"kind": "function", "line": 205, "name": "test_security_non_json_safe_attributes_rejected", "signature": "def test_security_non_json_safe_attributes_rejected()"}, {"kind": "function", "line": 212, "name": "test_security_bytes_top_level_parsed_rejected", "signature": "def test_security_bytes_top_level_parsed_rejected()"}, {"kind": "function", "line": 219, "name": "test_security_non_string_dict_key_rejected", "signature": "def test_security_non_string_dict_key_rejected()"}, {"kind": "function", "line": 226, "name": "test_security_arbitrary_object_rejected", "signature": "def test_security_arbitrary_object_rejected()"}, {"doc": "A hostile object nested inside a list must also be rejected (recursion).", "kind": "function", "line": 236, "name": "test_security_nested_object_inside_list_rejected", "signature": "def test_security_nested_object_inside_list_rejected()"}, {"doc": "The rejection message must name the offending type, not the input content.", "kind": "function", "line": 247, "name": "test_security_arbitrary_object_message_names_the_type", "signature": "def test_security_arbitrary_object_message_names_the_type()"}, {"kind": "function", "line": 260, "name": "test_security_non_string_key_message_names_the_problem", "signature": "def test_security_non_string_key_message_names_the_problem()"}, {"doc": "The rejection message for an arbitrary object is the exact contract text.", "kind": "function", "line": 270, "name": "test_security_arbitrary_object_message_exact", "signature": "def test_security_arbitrary_object_message_exact()"}, {"kind": "function", "line": 287, "name": "test_length_caps_are_positive", "signature": "def test_length_caps_are_positive()"}, {"kind": "class", "line": 227, "name": "_Hostile", "signature": "class _Hostile"}, {"kind": "class", "line": 238, "name": "_Hostile", "signature": "class _Hostile"}, {"kind": "class", "line": 249, "name": "_WeirdName", "signature": "class _WeirdName"}, {"kind": "class", "line": 272, "name": "_WeirdName", "signature": "class _WeirdName"}]}, {"doc": "OpenAPI BDD.", "id": "tests/test_openapi.py", "kind": "module", "label": "test_openapi.py", "language": "py", "sha256": "b2fa5914c67048bb", "symbol_count": 1, "symbols": [{"kind": "function", "line": 5, "name": "test_openapi_lists_healthz_without_auth_and_run_with_auth", "signature": "def test_openapi_lists_healthz_without_auth_and_run_with_auth()"}]}, {"doc": "M1 ops_observability — BDD red. Debe fallar hasta implementar módulo.", "id": "tests/test_ops_observability.py", "kind": "module", "label": "test_ops_observability.py", "language": "py", "sha256": "7a3ef5fb023ae253", "symbol_count": 6, "symbols": [{"kind": "function", "line": 8, "name": "test_healthz_no_auth_ok", "signature": "def test_healthz_no_auth_ok()"}, {"kind": "function", "line": 17, "name": "test_readyz_ready_and_not_ready", "signature": "def test_readyz_ready_and_not_ready()"}, {"kind": "function", "line": 26, "name": "test_metrics_counters_and_render", "signature": "def test_metrics_counters_and_render()"}, {"kind": "function", "line": 44, "name": "test_metrics_label_sanitised_no_newline", "signature": "def test_metrics_label_sanitised_no_newline()"}, {"kind": "function", "line": 61, "name": "test_json_logs_opt_in_and_legacy", "signature": "def test_json_logs_opt_in_and_legacy()"}, {"kind": "function", "line": 74, "name": "test_metrics_thread_safe", "signature": "def test_metrics_thread_safe()"}]}, {"doc": "BDD tests for operator-OPSEC contact classification + passive-only filter.  Ported from the standalone ``_test_passive.py`` validator into pytest. Offline: exercises the registry and the orchestrator selector.", "id": "tests/test_opsec_contact.py", "kind": "module", "label": "test_opsec_contact.py", "language": "py", "sha256": "338b06ea18750c90", "symbol_count": 14, "symbols": [{"kind": "function", "line": 19, "name": "_registry", "signature": "def _registry()"}, {"kind": "class", "line": 25, "name": "TestContactLevel", "signature": "class TestContactLevel"}, {"kind": "class", "line": 36, "name": "TestRegistryContact", "signature": "class TestRegistryContact"}, {"kind": "class", "line": 53, "name": "TestBrokerTagging", "signature": "class TestBrokerTagging"}, {"kind": "class", "line": 61, "name": "TestSelectorCeiling", "signature": "class TestSelectorCeiling"}, {"kind": "method", "line": 26, "name": "test_ordering", "signature": "def test_ordering(self)"}, {"kind": "method", "line": 29, "name": "test_unknown_is_active", "signature": "def test_unknown_is_active(self)"}, {"kind": "method", "line": 32, "name": "test_default_is_passive", "signature": "def test_default_is_passive(self)"}, {"kind": "method", "line": 37, "name": "test_sources_load", "signature": "def test_sources_load(self)"}, {"kind": "method", "line": 40, "name": "test_all_sources_carry_known_class", "signature": "def test_all_sources_carry_known_class(self)"}, {"kind": "method", "line": 45, "name": "test_passive_filter_excludes_broker_active", "signature": "def test_passive_filter_excludes_broker_active(self)"}, {"kind": "method", "line": 54, "name": "test_hackertarget_probes_are_broker", "signature": "def test_hackertarget_probes_are_broker(self)"}, {"kind": "method", "line": 62, "name": "test_selector_drops_broker_even_when_named", "signature": "def test_selector_drops_broker_even_when_named(self)"}, {"kind": "method", "line": 72, "name": "test_selector_keeps_broker_without_ceiling", "signature": "def test_selector_keeps_broker_without_ceiling(self)"}]}, {"doc": "M0 RED tests: fanout normalisation + callback parity + no duplicated methods.", "id": "tests/test_orchestrator_fanout.py", "kind": "module", "label": "test_orchestrator_fanout.py", "language": "py", "sha256": "1302cf4c6593022d", "symbol_count": 9, "symbols": [{"kind": "function", "line": 10, "name": "_src", "signature": "def _src(name, binary)"}, {"kind": "function", "line": 19, "name": "test_no_duplicated_method_defs", "signature": "def test_no_duplicated_method_defs()"}, {"kind": "function", "line": 28, "name": "_norm", "signature": "def _norm(items, sources)"}, {"kind": "function", "line": 33, "name": "test_exception_becomes_error_observation", "signature": "def test_exception_becomes_error_observation()"}, {"kind": "function", "line": 41, "name": "test_bad_shape_becomes_error_observation", "signature": "def test_bad_shape_becomes_error_observation()"}, {"kind": "function", "line": 47, "name": "test_mixed_http_and_system_app_keep_own_source", "signature": "def test_mixed_http_and_system_app_keep_own_source()"}, {"kind": "function", "line": 56, "name": "test_execute_source_forwards_on_done_to_system_app", "signature": "def test_execute_source_forwards_on_done_to_system_app()"}, {"kind": "function", "line": 67, "name": "test_system_app_failure_fires_on_done", "signature": "def test_system_app_failure_fires_on_done()"}, {"kind": "function", "line": 68, "name": "go", "signature": "def go()"}]}, {"doc": "BDD tests for estorides_core.pagination.  These tests verify the pagination strategy config, param building, cursor extraction, and result counting — all pure functions, no I/O.", "id": "tests/test_pagination.py", "kind": "module", "label": "test_pagination.py", "language": "py", "sha256": "080029cbb423494a", "symbol_count": 38, "symbols": [{"doc": "PG1: page param increments with each page number.", "kind": "class", "line": 21, "name": "TestPageStrategy", "signature": "class TestPageStrategy"}, {"doc": "PG2: offset advances by page_size each page.", "kind": "class", "line": 48, "name": "TestOffsetStrategy", "signature": "class TestOffsetStrategy"}, {"doc": "PG3: cursor extracted from response body.", "kind": "class", "line": 78, "name": "TestCursorStrategy", "signature": "class TestCursorStrategy"}, {"doc": "PG4: default config means disabled.", "kind": "class", "line": 128, "name": "TestNoPagination", "signature": "class TestNoPagination"}, {"doc": "PG5: fewer results than page_size signals last page.", "kind": "class", "line": 151, "name": "TestPartialPage", "signature": "class TestPartialPage"}, {"doc": "PG6: max_pages limits total requests.", "kind": "class", "line": 180, "name": "TestMaxPages", "signature": "class TestMaxPages"}, {"doc": "PaginationConfig construction from raw dict.", "kind": "class", "line": 205, "name": "TestFromDict", "signature": "class TestFromDict"}, {"kind": "method", "line": 24, "name": "test_first_page_is_one", "signature": "def test_first_page_is_one(self)"}, {"kind": "method", "line": 29, "name": "test_second_page_increments", "signature": "def test_second_page_increments(self)"}, {"kind": "method", "line": 34, "name": "test_default_param_name", "signature": "def test_default_param_name(self)"}, {"kind": "method", "line": 39, "name": "test_no_pagination_returns_empty", "signature": "def test_no_pagination_returns_empty(self)"}, {"kind": "method", "line": 51, "name": "test_first_page_offset_zero", "signature": "def test_first_page_offset_zero(self)"}, {"kind": "method", "line": 56, "name": "test_second_page_offset_25", "signature": "def test_second_page_offset_25(self)"}, {"kind": "method", "line": 61, "name": "test_third_page_offset_50", "signature": "def test_third_page_offset_50(self)"}, {"kind": "method", "line": 66, "name": "test_custom_param_names", "signature": "def test_custom_param_names(self)"}, {"kind": "method", "line": 81, "name": "test_extracts_cursor_from_simple_path", "signature": "def test_extracts_cursor_from_simple_path(self)"}, {"kind": "method", "line": 86, "name": "test_extracts_cursor_from_nested_path", "signature": "def test_extracts_cursor_from_nested_path(self)"}, {"kind": "method", "line": 91, "name": "test_missing_path_returns_none", "signature": "def test_missing_path_returns_none(self)"}, {"kind": "method", "line": 95, "name": "test_empty_cursor_returns_none", "signature": "def test_empty_cursor_returns_none(self)"}, {"kind": "method", "line": 99, "name": "test_null_cursor_returns_none", "signature": "def test_null_cursor_returns_none(self)"}, {"kind": "method", "line": 103, "name": "test_non_dict_response_returns_none", "signature": "def test_non_dict_response_returns_none(self)"}, {"kind": "method", "line": 107, "name": "test_disabled_strategy_returns_none", "signature": "def test_disabled_strategy_returns_none(self)"}, {"kind": "method", "line": 111, "name": "test_build_params_empty_for_cursor", "signature": "def test_build_params_empty_for_cursor(self)"}, {"kind": "method", "line": 116, "name": "test_cursor_custom_param", "signature": "def test_cursor_custom_param(self)"}, {"kind": "method", "line": 131, "name": "test_default_config_disabled", "signature": "def test_default_config_disabled(self)"}, {"kind": "method", "line": 135, "name": "test_empty_dict_disabled", "signature": "def test_empty_dict_disabled(self)"}, {"kind": "method", "line": 139, "name": "test_none_disabled", "signature": "def test_none_disabled(self)"}, {"kind": "method", "line": 143, "name": "test_enabled_when_strategy_set", "signature": "def test_enabled_when_strategy_set(self)"}, {"kind": "method", "line": 154, "name": "test_partial_page_detected", "signature": "def test_partial_page_detected(self)"}, {"kind": "method", "line": 159, "name": "test_full_page_not_detected_as_partial", "signature": "def test_full_page_not_detected_as_partial(self)"}, {"kind": "method", "line": 164, "name": "test_list_response_counted_directly", "signature": "def test_list_response_counted_directly(self)"}, {"kind": "method", "line": 168, "name": "test_using_custom_response_list_path", "signature": "def test_using_custom_response_list_path(self)"}, {"kind": "method", "line": 183, "name": "test_default_max_pages", "signature": "def test_default_max_pages(self)"}, {"kind": "method", "line": 187, "name": "test_custom_max_pages", "signature": "def test_custom_max_pages(self)"}, {"kind": "method", "line": 193, "name": "test_zero_page_size_means_no_check", "signature": "def test_zero_page_size_means_no_check(self)"}, {"kind": "method", "line": 208, "name": "test_all_fields_mapped", "signature": "def test_all_fields_mapped(self)"}, {"kind": "method", "line": 230, "name": "test_partial_dict_uses_defaults", "signature": "def test_partial_dict_uses_defaults(self)"}, {"kind": "method", "line": 236, "name": "test_empty_string_strategy_disabled", "signature": "def test_empty_string_strategy_disabled(self)"}]}, {"doc": "BDD / regression tests for the remote-JSON parser layer.  Doctrine (`parsers.py` header): every parser MUST be total — any unrecognised input returns an empty container, never raises. The orchestrator trusts that contract, so a remote source returning `null` where an object is expected must not abort a run.  - P1: known malformed payloads return empty containers, no raise - P2: valid payloads still parse (no over-eager guard regression)", "id": "tests/test_parsers.py", "kind": "module", "label": "test_parsers.py", "language": "py", "sha256": "b5957de29fb32a64", "symbol_count": 9, "symbols": [{"doc": "Given a remote payload with nulls/strings where objects belong.", "kind": "class", "line": 19, "name": "TestP1Totality", "signature": "class TestP1Totality"}, {"kind": "class", "line": 50, "name": "TestP2NoRegression", "signature": "class TestP2NoRegression"}, {"kind": "method", "line": 35, "name": "test_parser_is_total", "signature": "def test_parser_is_total(self, parser_name, payload)"}, {"kind": "method", "line": 39, "name": "test_otx_null_entry_skipped", "signature": "def test_otx_null_entry_skipped(self)"}, {"kind": "method", "line": 43, "name": "test_keybase_missing_them", "signature": "def test_keybase_missing_them(self)"}, {"kind": "method", "line": 46, "name": "test_wayback_non_list_header", "signature": "def test_wayback_non_list_header(self)"}, {"kind": "method", "line": 51, "name": "test_otx_valid", "signature": "def test_otx_valid(self)"}, {"kind": "method", "line": 58, "name": "test_keybase_valid", "signature": "def test_keybase_valid(self)"}, {"kind": "method", "line": 72, "name": "test_ethplorer_valid", "signature": "def test_ethplorer_valid(self)"}]}, {"doc": "ATDD + BDD tests for estorides_core.pdns_monitor.  Implements the Given-When-Then contracts declared in ``spec/pdns_monitor.md``.", "id": "tests/test_pdns_monitor.py", "kind": "module", "label": "test_pdns_monitor.py", "language": "py", "sha256": "7d6d5c7c0edea62a", "symbol_count": 17, "symbols": [{"kind": "class", "line": 18, "name": "TestCtLogSubdomains", "signature": "class TestCtLogSubdomains"}, {"kind": "class", "line": 32, "name": "TestIPHistory", "signature": "class TestIPHistory"}, {"kind": "class", "line": 47, "name": "TestNoData", "signature": "class TestNoData"}, {"kind": "class", "line": 55, "name": "TestNewCertificates", "signature": "class TestNewCertificates"}, {"kind": "class", "line": 76, "name": "TestNoAxfr", "signature": "class TestNoAxfr"}, {"kind": "class", "line": 85, "name": "TestWildcardCert", "signature": "class TestWildcardCert"}, {"kind": "class", "line": 102, "name": "TestSubdomainStatusClassification", "signature": "class TestSubdomainStatusClassification"}, {"kind": "class", "line": 111, "name": "TestMonitorPollInterval", "signature": "class TestMonitorPollInterval"}, {"kind": "method", "line": 19, "name": "test_returns_subdomains_from_ct", "signature": "def test_returns_subdomains_from_ct(self)"}, {"kind": "method", "line": 33, "name": "test_tracks_ip_changes", "signature": "def test_tracks_ip_changes(self)"}, {"kind": "method", "line": 48, "name": "test_empty_when_no_history", "signature": "def test_empty_when_no_history(self)"}, {"kind": "method", "line": 56, "name": "test_new_cert_with_san", "signature": "def test_new_cert_with_san(self)"}, {"kind": "method", "line": 77, "name": "test_no_zone_transfer_attempted", "signature": "def test_no_zone_transfer_attempted(self)"}, {"kind": "method", "line": 86, "name": "test_wildcard_cert_detected", "signature": "def test_wildcard_cert_detected(self)"}, {"kind": "method", "line": 103, "name": "test_active_status_when_resolves", "signature": "def test_active_status_when_resolves(self)"}, {"kind": "method", "line": 106, "name": "test_inactive_when_no_resolution", "signature": "def test_inactive_when_no_resolution(self)"}, {"kind": "method", "line": 112, "name": "test_minimum_poll_interval", "signature": "def test_minimum_poll_interval(self)"}]}, {"doc": "ATDD + BDD tests for estorides_core.people_intel.  Implements the Given-When-Then contracts declared in ``spec/people_intel.md``.", "id": "tests/test_people_intel.py", "kind": "module", "label": "test_people_intel.py", "language": "py", "sha256": "3b7b61f3b25ed7b9", "symbol_count": 18, "symbols": [{"kind": "function", "line": 18, "name": "_make_emp", "signature": "def _make_emp(name, role, email)"}, {"kind": "class", "line": 28, "name": "TestHappyPathEmployeeDiscovery", "signature": "class TestHappyPathEmployeeDiscovery"}, {"kind": "class", "line": 42, "name": "TestNoEmployees", "signature": "class TestNoEmployees"}, {"kind": "class", "line": 50, "name": "TestInvalidDomain", "signature": "class TestInvalidDomain"}, {"kind": "class", "line": 57, "name": "TestBreachPasswordContext", "signature": "class TestBreachPasswordContext"}, {"kind": "class", "line": 81, "name": "TestEmailPatternInference", "signature": "class TestEmailPatternInference"}, {"kind": "class", "line": 101, "name": "TestSingleEmailAmbiguous", "signature": "class TestSingleEmailAmbiguous"}, {"kind": "class", "line": 108, "name": "TestCrossBreachCorrelation", "signature": "class TestCrossBreachCorrelation"}, {"kind": "class", "line": 128, "name": "TestNoRawPasswords", "signature": "class TestNoRawPasswords"}, {"kind": "method", "line": 29, "name": "test_returns_employees_from_domain", "signature": "def test_returns_employees_from_domain(self)"}, {"kind": "method", "line": 43, "name": "test_empty_when_no_employees", "signature": "def test_empty_when_no_employees(self)"}, {"kind": "method", "line": 51, "name": "test_rejects_invalid_domain", "signature": "def test_rejects_invalid_domain(self)"}, {"kind": "method", "line": 58, "name": "test_breach_with_password_is_critical", "signature": "def test_breach_with_password_is_critical(self)"}, {"kind": "method", "line": 82, "name": "test_infers_first_dot_last_pattern", "signature": "def test_infers_first_dot_last_pattern(self)"}, {"kind": "method", "line": 91, "name": "test_infers_firstinitial_last_pattern", "signature": "def test_infers_firstinitial_last_pattern(self)"}, {"kind": "method", "line": 102, "name": "test_single_email_low_confidence", "signature": "def test_single_email_low_confidence(self)"}, {"kind": "method", "line": 109, "name": "test_multiple_breaches_increase_risk", "signature": "def test_multiple_breaches_increase_risk(self)"}, {"kind": "method", "line": 129, "name": "test_passwords_not_in_serialised_output", "signature": "def test_passwords_not_in_serialised_output(self)"}]}, {"doc": "BDD + property tests for probabilistic fusion.  These tests verify that :meth:`FusionStore.fuse_entity` and :meth:`FusionStore.fuse_relationship` use the reliability_scoring pipeline (source reliability + source type + corroboration) instead of the simplistic ``MAX()`` merge.", "id": "tests/test_probabilistic_fusion.py", "kind": "module", "label": "test_probabilistic_fusion.py", "language": "py", "sha256": "326f83de1fde72d9", "symbol_count": 27, "symbols": [{"kind": "function", "line": 17, "name": "_fs", "signature": "def _fs()"}, {"kind": "function", "line": 23, "name": "_teardown", "signature": "def _teardown(store, tmp)"}, {"kind": "function", "line": 30, "name": "_entity", "signature": "def _entity(etype, value, source, confidence)"}, {"doc": "PF1: tertiary first sighting → primary raises score.", "kind": "class", "line": 48, "name": "TestPrimarySourceRaisesLowConf", "signature": "class TestPrimarySourceRaisesLowConf"}, {"doc": "PF2: untrusted_webscraper (F) cannot beat 0.9 existing.", "kind": "class", "line": 77, "name": "TestTertiaryCannotOverride", "signature": "class TestTertiaryCannotOverride"}, {"doc": "PF3: multiple independent sources raise the score.", "kind": "class", "line": 110, "name": "TestCorroborationLiftsScore", "signature": "class TestCorroborationLiftsScore"}, {"doc": "PF4: a lower-confidence sighting never decreases the score.", "kind": "class", "line": 142, "name": "TestMergeMonotonic", "signature": "class TestMergeMonotonic"}, {"doc": "PF5: untrusted_webscraper first sighting is heavily discounted.", "kind": "class", "line": 170, "name": "TestFirstSightingWeighted", "signature": "class TestFirstSightingWeighted"}, {"doc": "PF6: relationship from untrusted source cannot override primary.", "kind": "class", "line": 192, "name": "TestRelationshipBayesian", "signature": "class TestRelationshipBayesian"}, {"doc": "Existing fusion store invariants must still hold.", "kind": "class", "line": 217, "name": "TestFusionStoreRegression", "signature": "class TestFusionStoreRegression"}, {"kind": "method", "line": 51, "name": "test_primary_source_raises_score", "signature": "def test_primary_source_raises_score(self)"}, {"kind": "method", "line": 80, "name": "test_untrusted_source_cannot_override", "signature": "def test_untrusted_source_cannot_override(self)"}, {"kind": "method", "line": 113, "name": "test_two_sources_are_better_than_one", "signature": "def test_two_sources_are_better_than_one(self)"}, {"kind": "method", "line": 145, "name": "test_lower_confidence_never_decreases", "signature": "def test_lower_confidence_never_decreases(self)"}, {"kind": "method", "line": 173, "name": "test_untrusted_first_sighting_discounted", "signature": "def test_untrusted_first_sighting_discounted(self)"}, {"kind": "method", "line": 195, "name": "test_relationship_untrusted_cannot_override", "signature": "def test_relationship_untrusted_cannot_override(self)"}, {"kind": "method", "line": 220, "name": "test_entity_id_deterministic", "signature": "def test_entity_id_deterministic(self)"}, {"kind": "method", "line": 229, "name": "test_entity_source_count_tracks", "signature": "def test_entity_source_count_tracks(self)"}, {"kind": "method", "line": 243, "name": "test_observation_count_advances", "signature": "def test_observation_count_advances(self)"}, {"kind": "method", "line": 259, "name": "test_empty_type_returns_empty", "signature": "def test_empty_type_returns_empty(self)"}, {"kind": "method", "line": 267, "name": "test_empty_value_returns_empty", "signature": "def test_empty_value_returns_empty(self)"}, {"kind": "method", "line": 275, "name": "test_add_observation_and_stats", "signature": "def test_add_observation_and_stats(self)"}, {"kind": "method", "line": 290, "name": "test_register_sources", "signature": "def test_register_sources(self)"}, {"kind": "method", "line": 305, "name": "test_search_entities", "signature": "def test_search_entities(self)"}, {"kind": "method", "line": 320, "name": "test_corroborated_properties", "signature": "def test_corroborated_properties(self)"}, {"kind": "method", "line": 333, "name": "test_get_entity_nonexistent", "signature": "def test_get_entity_nonexistent(self)"}, {"kind": "method", "line": 340, "name": "test_open_store_closes_gracefully", "signature": "def test_open_store_closes_gracefully(self)"}]}, {"doc": "M1 RED tests: query intent v2.", "id": "tests/test_query_intent.py", "kind": "module", "label": "test_query_intent.py", "language": "py", "sha256": "c1479ec8c0d38533", "symbol_count": 6, "symbols": [{"kind": "function", "line": 7, "name": "test_phone", "signature": "def test_phone()"}, {"kind": "function", "line": 12, "name": "test_mac", "signature": "def test_mac()"}, {"kind": "function", "line": 17, "name": "test_at_handle", "signature": "def test_at_handle()"}, {"kind": "function", "line": 22, "name": "test_url_normalises_to_host", "signature": "def test_url_normalises_to_host()"}, {"kind": "function", "line": 27, "name": "test_btc_uppercase_bech32", "signature": "def test_btc_uppercase_bech32()"}, {"kind": "function", "line": 31, "name": "test_backward_compat", "signature": "def test_backward_compat()"}]}, {"doc": "BDD tests for the recon_fusion module (Modulo 2g).  Each scenario maps to a Given-When-Then from spec/recon_fusion.md.", "id": "tests/test_recon_fusion.py", "kind": "module", "label": "test_recon_fusion.py", "language": "py", "sha256": "dd2c52e80a42e41c", "symbol_count": 33, "symbols": [{"kind": "function", "line": 14, "name": "_observation", "signature": "def _observation(source, category, parser, status, parsed)"}, {"kind": "function", "line": 31, "name": "_entity", "signature": "def _entity(etype, value, confidence, sources)"}, {"doc": "S1 -- Happy path: corroborated by 5 sources -> CRITICAL.", "kind": "class", "line": 46, "name": "TestS1CriticalCorroborated", "signature": "class TestS1CriticalCorroborated"}, {"doc": "S2 -- 2 sources with A/B reliability -> CRITICAL (via 2+ src + high rel).", "kind": "class", "line": 65, "name": "TestS2TwoReliableSources", "signature": "class TestS2TwoReliableSources"}, {"doc": "S3 -- Single high-reliability source -> MEDIUM.", "kind": "class", "line": 82, "name": "TestS3SingleHighReliability", "signature": "class TestS3SingleHighReliability"}, {"doc": "S4 -- Single F-reliability source -> NOISE.", "kind": "class", "line": 102, "name": "TestS4SingleLowReliability", "signature": "class TestS4SingleLowReliability"}, {"doc": "S5 -- No observations or entities -> empty result.", "kind": "class", "line": 117, "name": "TestS5EmptyInput", "signature": "class TestS5EmptyInput"}, {"doc": "S6 -- Direct match entity has boosted score.", "kind": "class", "line": 130, "name": "TestS6DirectMatchBoost", "signature": "class TestS6DirectMatchBoost"}, {"doc": "S7 -- Empty query raises ValueError.", "kind": "class", "line": 147, "name": "TestS7EmptyQuery", "signature": "class TestS7EmptyQuery"}, {"doc": "S8 -- Inconsistent thresholds raise ValueError.", "kind": "class", "line": 157, "name": "TestS8BadConfig", "signature": "class TestS8BadConfig"}, {"doc": "S9 -- None observations handled safely.", "kind": "class", "line": 173, "name": "TestS9NoneObservations", "signature": "class TestS9NoneObservations"}, {"doc": "S10 -- Entity without type is ignored.", "kind": "class", "line": 183, "name": "TestS10EntityWithoutType", "signature": "class TestS10EntityWithoutType"}, {"doc": "S11 -- Identical observations deduped to one.", "kind": "class", "line": 195, "name": "TestS11Dedup", "signature": "class TestS11Dedup"}, {"doc": "S12 -- Tiers ordered by relevance_score descending.", "kind": "class", "line": 212, "name": "TestS12Ordering", "signature": "class TestS12Ordering"}, {"doc": "Integration: multiple entities classified across tiers.", "kind": "class", "line": 227, "name": "TestIntegrationMultiEntity", "signature": "class TestIntegrationMultiEntity"}, {"doc": "FusionResult dataclass structural tests.", "kind": "class", "line": 252, "name": "TestFusionResultDataclass", "signature": "class TestFusionResultDataclass"}, {"doc": "RelevanceTier enum members and ordering.", "kind": "class", "line": 267, "name": "TestRelevanceTierEnum", "signature": "class TestRelevanceTierEnum"}, {"kind": "method", "line": 49, "name": "test_critical_with_5_sources", "signature": "def test_critical_with_5_sources(self)"}, {"kind": "method", "line": 68, "name": "test_two_reliable_sources_critical", "signature": "def test_two_reliable_sources_critical(self)"}, {"kind": "method", "line": 85, "name": "test_single_a_source_is_medium", "signature": "def test_single_a_source_is_medium(self)"}, {"kind": "method", "line": 105, "name": "test_single_f_source_is_noise", "signature": "def test_single_f_source_is_noise(self)"}, {"kind": "method", "line": 120, "name": "test_empty_observations_and_entities", "signature": "def test_empty_observations_and_entities(self)"}, {"kind": "method", "line": 133, "name": "test_direct_match_boosts_score", "signature": "def test_direct_match_boosts_score(self)"}, {"kind": "method", "line": 150, "name": "test_empty_query_raises", "signature": "def test_empty_query_raises(self)"}, {"kind": "method", "line": 160, "name": "test_bad_thresholds_raise", "signature": "def test_bad_thresholds_raise(self)"}, {"kind": "method", "line": 176, "name": "test_none_observations_safe", "signature": "def test_none_observations_safe(self)"}, {"kind": "method", "line": 186, "name": "test_entity_without_type_ignored", "signature": "def test_entity_without_type_ignored(self)"}, {"kind": "method", "line": 198, "name": "test_identical_observations_deduped", "signature": "def test_identical_observations_deduped(self)"}, {"kind": "method", "line": 215, "name": "test_tier_ordered_by_score", "signature": "def test_tier_ordered_by_score(self)"}, {"kind": "method", "line": 230, "name": "test_mixed_entities_across_tiers", "signature": "def test_mixed_entities_across_tiers(self)"}, {"kind": "method", "line": 255, "name": "test_fusion_result_serialisable", "signature": "def test_fusion_result_serialisable(self)"}, {"kind": "method", "line": 270, "name": "test_enum_members", "signature": "def test_enum_members(self)"}, {"kind": "method", "line": 277, "name": "test_enum_order_list", "signature": "def test_enum_order_list(self)"}]}, {"doc": "ATDD + BDD tests for estorides_export.recon_report.  Implements the Given-When-Then contracts declared in ``spec/recon_report.md``.", "id": "tests/test_recon_report.py", "kind": "module", "label": "test_recon_report.py", "language": "py", "sha256": "ee9ef3e23075a2ce", "symbol_count": 19, "symbols": [{"kind": "function", "line": 18, "name": "_make_meta", "signature": "def _make_meta()"}, {"kind": "class", "line": 28, "name": "TestFullReport", "signature": "class TestFullReport"}, {"kind": "class", "line": 42, "name": "TestExecutiveSummaryFindings", "signature": "class TestExecutiveSummaryFindings"}, {"kind": "class", "line": 57, "name": "TestMinimalReport", "signature": "class TestMinimalReport"}, {"kind": "class", "line": 72, "name": "TestTlpClassification", "signature": "class TestTlpClassification"}, {"kind": "class", "line": 98, "name": "TestRecommendationsOrdered", "signature": "class TestRecommendationsOrdered"}, {"kind": "class", "line": 106, "name": "TestCredentialsRedacted", "signature": "class TestCredentialsRedacted"}, {"kind": "class", "line": 119, "name": "TestSubdomainTree", "signature": "class TestSubdomainTree"}, {"kind": "class", "line": 133, "name": "TestSingleFindingReport", "signature": "class TestSingleFindingReport"}, {"kind": "method", "line": 29, "name": "test_contains_all_sections", "signature": "def test_contains_all_sections(self)"}, {"kind": "method", "line": 43, "name": "test_mentions_critical_findings", "signature": "def test_mentions_critical_findings(self)"}, {"kind": "method", "line": 58, "name": "test_minimal_report_with_no_findings", "signature": "def test_minimal_report_with_no_findings(self)"}, {"kind": "method", "line": 73, "name": "test_tlp_amber_in_header", "signature": "def test_tlp_amber_in_header(self)"}, {"doc": "Given a TLP:RED report, the executive summary must say RED too.\n\nRegression: the executive summary used to hardcode TLP:AMBER while\nthe header printed the caller's classification, so a RED report\ncarried two contradicting markings.", "kind": "method", "line": 81, "name": "test_classification_propagates_to_exec_summary", "signature": "def test_classification_propagates_to_exec_summary(self)"}, {"kind": "method", "line": 99, "name": "test_critical_first", "signature": "def test_critical_first(self)"}, {"kind": "method", "line": 107, "name": "test_aws_key_redacted", "signature": "def test_aws_key_redacted(self)"}, {"kind": "method", "line": 112, "name": "test_password_redacted", "signature": "def test_password_redacted(self)"}, {"kind": "method", "line": 120, "name": "test_ascii_tree_generated", "signature": "def test_ascii_tree_generated(self)"}, {"kind": "method", "line": 134, "name": "test_single_finding_prominent", "signature": "def test_single_finding_prominent(self)"}]}, {"doc": "ATDD + BDD tests for estorides_core.reliability_scoring.  These tests implement the Given-When-Then contracts declared in ``spec/reliability_scoring.md`` (module 2a). They MUST fail against the unwritten implementation (the red step of the cycle), and they MUST pass after the green step. Property-based invariants live in ``tests/properties/test_reliability_scoring_properties.py``.  Run from the project root::  .venv/bin/pytest tests/test_reliability_scoring.py -v", "id": "tests/test_reliability_scoring.py", "kind": "module", "label": "test_reliability_scoring.py", "language": "py", "sha256": "98ad275427531ed0", "symbol_count": 70, "symbols": [{"doc": "S1 del spec.", "kind": "class", "line": 47, "name": "TestHappyPathHighReliabilityCorroboratedFresh", "signature": "class TestHappyPathHighReliabilityCorroboratedFresh"}, {"doc": "S2 del spec.", "kind": "class", "line": 111, "name": "TestUnknownSourceFallsBackToDefault", "signature": "class TestUnknownSourceFallsBackToDefault"}, {"doc": "S3 del spec: una sola fuente, no importa lo fiable que sea, score = 0.", "kind": "class", "line": 171, "name": "TestZeroCorroborationYieldsZeroScore", "signature": "class TestZeroCorroborationYieldsZeroScore"}, {"doc": "S4 del spec.", "kind": "class", "line": 190, "name": "TestVeryOldObservationDecaysToZero", "signature": "class TestVeryOldObservationDecaysToZero"}, {"doc": "S5 del spec.", "kind": "class", "line": 227, "name": "TestInvalidInputRaisesValueError", "signature": "class TestInvalidInputRaisesValueError"}, {"doc": "S6 del spec: input del operador, posiblemente adversario, no rompe.", "kind": "class", "line": 272, "name": "TestHostileSourceNameIsHandledSafely", "signature": "class TestHostileSourceNameIsHandledSafely"}, {"doc": "S7 del spec.", "kind": "class", "line": 301, "name": "TestMergeReliableBeatsLessReliable", "signature": "class TestMergeReliableBeatsLessReliable"}, {"doc": "S8 del spec.", "kind": "class", "line": 368, "name": "TestMergeUnreliableCannotRaise", "signature": "class TestMergeUnreliableCannotRaise"}, {"doc": "S9 del spec: misma entrada → misma salida, bit a bit.", "kind": "class", "line": 414, "name": "TestDeterminism", "signature": "class TestDeterminism"}, {"doc": "S10 del spec: smoke test del contrato de cota.", "kind": "class", "line": 455, "name": "TestBoundedSmoke", "signature": "class TestBoundedSmoke"}, {"doc": "S11 del spec: mismo reliability, distinto source_type.", "kind": "class", "line": 485, "name": "TestSourceHierarchyPrimaryBeatsSecondaryBeatsTertiary", "signature": "class TestSourceHierarchyPrimaryBeatsSecondaryBeatsTertiary"}, {"doc": "S12 del spec: source type compensates for lower reliability.", "kind": "class", "line": 552, "name": "TestSourceHierarchyPrimaryCBeatsTertiaryA", "signature": "class TestSourceHierarchyPrimaryCBeatsTertiaryA"}, {"doc": "S13 del spec: lookup known and unknown source names.", "kind": "class", "line": 596, "name": "TestSourceTypeFromName", "signature": "class TestSourceTypeFromName"}, {"doc": "S14 del spec: merge with source type hierarchy.", "kind": "class", "line": 630, "name": "TestSourceHierarchyInMerge", "signature": "class TestSourceHierarchyInMerge"}, {"doc": "S15 del spec: source_type_weight is always in {1.00, 0.85, 0.60}.", "kind": "class", "line": 664, "name": "TestSourceTypeWeightsBounded", "signature": "class TestSourceTypeWeightsBounded"}, {"doc": "`reliability_weight*` centralise the mapping that used to be copied\ninto change_detection / recon_fusion / hypothesis_engine.", "kind": "class", "line": 694, "name": "TestReliabilityWeightHelpers", "signature": "class TestReliabilityWeightHelpers"}, {"kind": "method", "line": 50, "name": "test_score_in_high_band", "signature": "def test_score_in_high_band(self)"}, {"kind": "method", "line": 69, "name": "test_reliability_weight_is_one", "signature": "def test_reliability_weight_is_one(self)"}, {"kind": "method", "line": 78, "name": "test_freshness_weight_is_one_when_age_zero", "signature": "def test_freshness_weight_is_one_when_age_zero(self)"}, {"kind": "method", "line": 88, "name": "test_corroboration_weight_matches_log10", "signature": "def test_corroboration_weight_matches_log10(self)"}, {"kind": "method", "line": 97, "name": "test_credibility_weight_for_probably_true", "signature": "def test_credibility_weight_for_probably_true(self)"}, {"kind": "method", "line": 122, "name": "test_unknown_source_returns_default", "signature": "def test_unknown_source_returns_default(self, name)"}, {"kind": "method", "line": 128, "name": "test_unknown_source_produces_weak_score_with_one_corroboration", "signature": "def test_unknown_source_produces_weak_score_with_one_corroboration(self)"}, {"kind": "method", "line": 148, "name": "test_unknown_source_with_many_corroborators_reaches_high_band", "signature": "def test_unknown_source_with_many_corroborators_reaches_high_band(self)"}, {"kind": "method", "line": 174, "name": "test_zero_corroboration_collapses_score", "signature": "def test_zero_corroboration_collapses_score(self)"}, {"kind": "method", "line": 193, "name": "test_one_year_old_with_low_corroboration_decays", "signature": "def test_one_year_old_with_low_corroboration_decays(self)"}, {"doc": "Una observación más vieja siempre tiene freshness menor o igual.", "kind": "method", "line": 206, "name": "test_freshness_is_monotonically_decreasing", "signature": "def test_freshness_is_monotonically_decreasing(self)"}, {"kind": "method", "line": 230, "name": "test_negative_corroboration_count_raises", "signature": "def test_negative_corroboration_count_raises(self)"}, {"kind": "method", "line": 237, "name": "test_negative_observation_age_raises", "signature": "def test_negative_observation_age_raises(self)"}, {"kind": "method", "line": 245, "name": "test_base_confidence_out_of_range_raises", "signature": "def test_base_confidence_out_of_range_raises(self, bad)"}, {"kind": "method", "line": 252, "name": "test_half_life_zero_raises", "signature": "def test_half_life_zero_raises(self)"}, {"kind": "method", "line": 260, "name": "test_half_life_negative_raises", "signature": "def test_half_life_negative_raises(self)"}, {"kind": "method", "line": 290, "name": "test_hostile_name_does_not_raise", "signature": "def test_hostile_name_does_not_raise(self, hostile)"}, {"kind": "method", "line": 304, "name": "test_new_a_source_raises_score", "signature": "def test_new_a_source_raises_score(self)"}, {"kind": "method", "line": 321, "name": "test_merge_takes_max_of_existing_and_new", "signature": "def test_merge_takes_max_of_existing_and_new(self)"}, {"kind": "method", "line": 337, "name": "test_merge_keeps_existing_when_new_is_weaker", "signature": "def test_merge_keeps_existing_when_new_is_weaker(self)"}, {"kind": "method", "line": 352, "name": "test_merge_result_is_always_bounded", "signature": "def test_merge_result_is_always_bounded(self)"}, {"kind": "method", "line": 371, "name": "test_f_source_against_strong_existing_keeps_existing", "signature": "def test_f_source_against_strong_existing_keeps_existing(self)"}, {"kind": "method", "line": 386, "name": "test_merge_existing_out_of_range_raises", "signature": "def test_merge_existing_out_of_range_raises(self)"}, {"kind": "method", "line": 398, "name": "test_merge_new_observation_out_of_range_raises", "signature": "def test_merge_new_observation_out_of_range_raises(self)"}, {"kind": "method", "line": 417, "name": "test_compute_confidence_is_pure", "signature": "def test_compute_confidence_is_pure(self)"}, {"kind": "method", "line": 430, "name": "test_merge_confidence_is_pure", "signature": "def test_merge_confidence_is_pure(self)"}, {"kind": "method", "line": 458, "name": "test_reliability_weights_match_nato_admiralty", "signature": "def test_reliability_weights_match_nato_admiralty(self)"}, {"kind": "method", "line": 467, "name": "test_credibility_weights_match_nato_admiralty", "signature": "def test_credibility_weights_match_nato_admiralty(self)"}, {"kind": "method", "line": 475, "name": "test_default_half_life_is_thirty_days", "signature": "def test_default_half_life_is_thirty_days(self)"}, {"kind": "method", "line": 478, "name": "test_default_credibility_is_cannot_be_judged", "signature": "def test_default_credibility_is_cannot_be_judged(self)"}, {"kind": "method", "line": 488, "name": "test_primary_tertiary_score_order", "signature": "def test_primary_tertiary_score_order(self)"}, {"kind": "method", "line": 521, "name": "test_primary_weight_is_one", "signature": "def test_primary_weight_is_one(self)"}, {"kind": "method", "line": 530, "name": "test_secondary_weight_is_085", "signature": "def test_secondary_weight_is_085(self)"}, {"kind": "method", "line": 539, "name": "test_tertiary_weight_is_060", "signature": "def test_tertiary_weight_is_060(self)"}, {"kind": "method", "line": 555, "name": "test_primary_c_beats_tertiary_a", "signature": "def test_primary_c_beats_tertiary_a(self)"}, {"kind": "method", "line": 582, "name": "test_primary_c_weight", "signature": "def test_primary_c_weight(self)"}, {"kind": "method", "line": 599, "name": "test_rdap_is_primary", "signature": "def test_rdap_is_primary(self)"}, {"kind": "method", "line": 602, "name": "test_leakcheck_is_tertiary", "signature": "def test_leakcheck_is_tertiary(self)"}, {"kind": "method", "line": 605, "name": "test_wikidata_is_secondary", "signature": "def test_wikidata_is_secondary(self)"}, {"kind": "method", "line": 608, "name": "test_shodan_is_secondary", "signature": "def test_shodan_is_secondary(self)"}, {"kind": "method", "line": 611, "name": "test_unknown_falls_back_to_default", "signature": "def test_unknown_falls_back_to_default(self)"}, {"kind": "method", "line": 614, "name": "test_none_falls_back", "signature": "def test_none_falls_back(self)"}, {"kind": "method", "line": 617, "name": "test_empty_falls_back", "signature": "def test_empty_falls_back(self)"}, {"kind": "method", "line": 620, "name": "test_hostile_input_does_not_raise", "signature": "def test_hostile_input_does_not_raise(self)"}, {"kind": "method", "line": 633, "name": "test_new_primary_raises_score", "signature": "def test_new_primary_raises_score(self)"}, {"kind": "method", "line": 646, "name": "test_new_tertiary_does_not_raise_weak_existing", "signature": "def test_new_tertiary_does_not_raise_weak_existing(self)"}, {"kind": "method", "line": 667, "name": "test_source_type_weights_are_exact", "signature": "def test_source_type_weights_are_exact(self)"}, {"kind": "method", "line": 672, "name": "test_primary_always_gives_one", "signature": "def test_primary_always_gives_one(self)"}, {"kind": "method", "line": 681, "name": "test_tertiary_always_gives_060", "signature": "def test_tertiary_always_gives_060(self)"}, {"kind": "method", "line": 698, "name": "test_known_source_letter", "signature": "def test_known_source_letter(self)"}, {"kind": "method", "line": 703, "name": "test_unknown_source_falls_back_to_default", "signature": "def test_unknown_source_falls_back_to_default(self)"}, {"kind": "method", "line": 708, "name": "test_override_wins", "signature": "def test_override_wins(self)"}, {"kind": "method", "line": 711, "name": "test_invalid_override_falls_back", "signature": "def test_invalid_override_falls_back(self)"}, {"kind": "method", "line": 716, "name": "test_letter_helper", "signature": "def test_letter_helper(self)"}]}, {"doc": "Retry policy BDD.", "id": "tests/test_retry_policy.py", "kind": "module", "label": "test_retry_policy.py", "language": "py", "sha256": "912f284526df29f9", "symbol_count": 2, "symbols": [{"kind": "function", "line": 5, "name": "test_delays_geometric_and_capped", "signature": "def test_delays_geometric_and_capped()"}, {"kind": "function", "line": 14, "name": "test_client_defaults_to_central_policy", "signature": "def test_client_defaults_to_central_policy()"}]}, {"doc": "BDD tests for the bug-bounty scope classifier (`estorides_core.scope`).  Ported from the standalone ``_test_scope.py`` validator into the pytest suite so CI actually runs it.", "id": "tests/test_scope.py", "kind": "module", "label": "test_scope.py", "language": "py", "sha256": "30f207e3d7f57d90", "symbol_count": 15, "symbols": [{"kind": "class", "line": 32, "name": "TestNormaliseAsset", "signature": "class TestNormaliseAsset"}, {"kind": "class", "line": 43, "name": "TestParseRules", "signature": "class TestParseRules"}, {"kind": "class", "line": 48, "name": "TestClassify", "signature": "class TestClassify"}, {"kind": "class", "line": 79, "name": "TestBuildReport", "signature": "class TestBuildReport"}, {"kind": "method", "line": 33, "name": "test_strip_scheme_path_port", "signature": "def test_strip_scheme_path_port(self)"}, {"kind": "method", "line": 36, "name": "test_strip_trailing_dot", "signature": "def test_strip_trailing_dot(self)"}, {"kind": "method", "line": 39, "name": "test_ipv6_brackets_stripped", "signature": "def test_ipv6_brackets_stripped(self)"}, {"kind": "method", "line": 44, "name": "test_skips_blanks_and_comments", "signature": "def test_skips_blanks_and_comments(self)"}, {"kind": "method", "line": 49, "name": "test_wildcard_matches_subdomain_and_apex", "signature": "def test_wildcard_matches_subdomain_and_apex(self)"}, {"kind": "method", "line": 54, "name": "test_regex_rule", "signature": "def test_regex_rule(self)"}, {"kind": "method", "line": 59, "name": "test_cidr_and_single_ip", "signature": "def test_cidr_and_single_ip(self)"}, {"kind": "method", "line": 65, "name": "test_out_of_scope_precedence", "signature": "def test_out_of_scope_precedence(self)"}, {"kind": "method", "line": 70, "name": "test_foreign_host_unknown", "signature": "def test_foreign_host_unknown(self)"}, {"kind": "method", "line": 74, "name": "test_empty_asset_unknown", "signature": "def test_empty_asset_unknown(self)"}, {"kind": "method", "line": 80, "name": "test_dedup_hosts_ips_and_buckets", "signature": "def test_dedup_hosts_ips_and_buckets(self)"}]}, {"doc": "BDD/ATDD suite for the `search_telemetry` module (spec/search_telemetry.md).  Each test maps to a Given-When-Then scenario in the spec. The pure-logic scenarios (S1-S6, S9, S10) exercise the module directly; the integration scenarios (S7, S8, S11) render `index.html` through Jinja and assert the served chrome honours the brand/emoji and single-source-of-truth invariants.", "id": "tests/test_search_telemetry.py", "kind": "module", "label": "test_search_telemetry.py", "language": "py", "sha256": "650ed6d69e86da5a", "symbol_count": 19, "symbols": [{"doc": "Render `index.html` exactly as the web layer does, telemetry included.", "kind": "function", "line": 36, "name": "_render_index", "signature": "def _render_index()"}, {"kind": "function", "line": 52, "name": "test_s1_determinate_progress_midsearch", "signature": "def test_s1_determinate_progress_midsearch()"}, {"kind": "function", "line": 67, "name": "test_s2_indeterminate_progress", "signature": "def test_s2_indeterminate_progress()"}, {"kind": "function", "line": 79, "name": "test_s3_completion_stops_spinner", "signature": "def test_s3_completion_stops_spinner()"}, {"kind": "function", "line": 90, "name": "test_s4_out_of_range_is_clamped", "signature": "def test_s4_out_of_range_is_clamped()"}, {"kind": "function", "line": 103, "name": "test_s5_unknown_phase_rejected", "signature": "def test_s5_unknown_phase_rejected()"}, {"kind": "function", "line": 114, "name": "test_s6_catalog_is_brand_and_emoji_clean", "signature": "def test_s6_catalog_is_brand_and_emoji_clean()"}, {"kind": "function", "line": 132, "name": "test_s7_rendered_template_has_no_third_party_brand", "signature": "def test_s7_rendered_template_has_no_third_party_brand()"}, {"kind": "function", "line": 141, "name": "test_s8_rendered_chrome_has_no_emoji", "signature": "def test_s8_rendered_chrome_has_no_emoji()"}, {"kind": "function", "line": 153, "name": "test_s9_brand_predicate_boundaries", "signature": "def test_s9_brand_predicate_boundaries()"}, {"kind": "function", "line": 163, "name": "_valid_kwargs", "signature": "def _valid_kwargs()"}, {"kind": "function", "line": 178, "name": "test_s10_empty_brand_rejected", "signature": "def test_s10_empty_brand_rejected()"}, {"kind": "function", "line": 185, "name": "test_s10_no_tips_rejected", "signature": "def test_s10_no_tips_rejected()"}, {"kind": "function", "line": 192, "name": "test_s10_duplicate_phase_rejected", "signature": "def test_s10_duplicate_phase_rejected()"}, {"kind": "function", "line": 205, "name": "test_s10_emoji_in_catalog_rejected", "signature": "def test_s10_emoji_in_catalog_rejected()"}, {"kind": "function", "line": 212, "name": "test_s10_brand_collision_rejected", "signature": "def test_s10_brand_collision_rejected()"}, {"kind": "function", "line": 219, "name": "test_s10_missing_sentinel_phase_rejected", "signature": "def test_s10_missing_sentinel_phase_rejected()"}, {"kind": "function", "line": 233, "name": "test_s11_template_renders_from_catalog", "signature": "def test_s11_template_renders_from_catalog()"}, {"kind": "function", "line": 247, "name": "test_default_telemetry_is_a_shared_instance", "signature": "def test_default_telemetry_is_a_shared_instance()"}]}, {"id": "tests/test_security_remediation.py", "kind": "module", "label": "test_security_remediation.py", "language": "py", "sha256": "3932e1b86e67c046", "symbol_count": 67, "symbols": [{"doc": "BDD S1, S2: ssrf_guard._resolve log messages must not contain the\nhostname or exception detail.", "kind": "class", "line": 25, "name": "TestSsrfLogSanitisation", "signature": "class TestSsrfLogSanitisation"}, {"doc": "BDD S3, S4: encryption error endpoints must not leak str(e).", "kind": "class", "line": 75, "name": "TestInfoExposureEncryption", "signature": "class TestInfoExposureEncryption"}, {"doc": "BDD S5, S6: source file operations must not leak str(e).", "kind": "class", "line": 176, "name": "TestInfoExposureSourceOps", "signature": "class TestInfoExposureSourceOps"}, {"doc": "BDD S7: the HTTPS redirect must use request.host, not request.url.", "kind": "class", "line": 260, "name": "TestHttpsRedirectSafety", "signature": "class TestHttpsRedirectSafety"}, {"doc": "BDD S10: .github/workflows/ci.yml must have permissions: read-all.", "kind": "class", "line": 306, "name": "TestCiWorkflowPermissions", "signature": "class TestCiWorkflowPermissions"}, {"doc": "BDD S11: osiris endpoint exceptions must return generic error.", "kind": "class", "line": 336, "name": "TestOsirisExceptionSafety", "signature": "class TestOsirisExceptionSafety"}, {"doc": "Verify the HTTP-to-HTTPS redirect in web_security.py is safe.", "kind": "class", "line": 399, "name": "TestWebSecurityRedirect", "signature": "class TestWebSecurityRedirect"}, {"doc": "BDD S8, S9: verify no innerHTML with unsanitized data in JS.", "kind": "class", "line": 431, "name": "TestJavaScriptDomSafety", "signature": "class TestJavaScriptDomSafety"}, {"doc": "Alert webhook URLs (incl. user-supplied `channel` URLs) are validated\nby the central SSRF guard before any socket opens. Treats all webhook\ndestinations as hostile.", "kind": "class", "line": 498, "name": "TestAlerterSsrf", "signature": "class TestAlerterSsrf"}, {"doc": "A webhook that 302-redirects to link-local/cloud-metadata must NOT be\nfollowed: the SSRF guard only vetted the first hop.", "kind": "class", "line": 538, "name": "TestAlerterNoRedirect", "signature": "class TestAlerterNoRedirect"}, {"doc": "DOMPurify pipeline: remote text reaches the DOM only through\nDOMPurify.sanitize (the sanitizer CodeQL models), with a fail-closed\nplain-text fallback when the vendored library is missing.", "kind": "class", "line": 592, "name": "TestTooltipSinkHardening", "signature": "class TestTooltipSinkHardening"}, {"kind": "method", "line": 29, "name": "test_dns_failure_log_omits_hostname", "signature": "def test_dns_failure_log_omits_hostname(self, caplog)"}, {"kind": "method", "line": 43, "name": "test_dns_failure_log_omits_ip_in_hostname", "signature": "def test_dns_failure_log_omits_ip_in_hostname(self, caplog)"}, {"kind": "method", "line": 55, "name": "test_dns_failure_log_contains_host_length_not_host", "signature": "def test_dns_failure_log_contains_host_length_not_host(self, caplog)"}, {"kind": "method", "line": 79, "name": "app", "signature": "def app(self)"}, {"kind": "method", "line": 86, "name": "_make_export_route", "signature": "def _make_export_route(self, app, raise_val, error_msg, status)"}, {"kind": "method", "line": 118, "name": "_make_export_route_fixed", "signature": "def _make_export_route_fixed(self, app, raise_val, error_msg, status)"}, {"kind": "method", "line": 151, "name": "test_encryption_valueerror_leaks_detail", "signature": "def test_encryption_valueerror_leaks_detail(self, app)"}, {"kind": "method", "line": 159, "name": "test_encryption_valueerror_fixed_no_detail", "signature": "def test_encryption_valueerror_fixed_no_detail(self, app)"}, {"kind": "method", "line": 167, "name": "test_encryption_runtimeerror_fixed_no_detail", "signature": "def test_encryption_runtimeerror_fixed_no_detail(self, app)"}, {"kind": "method", "line": 180, "name": "app", "signature": "def app(self)"}, {"kind": "method", "line": 187, "name": "test_source_delete_keyerror_fixed", "signature": "def test_source_delete_keyerror_fixed(self, app)"}, {"kind": "method", "line": 208, "name": "test_source_create_valueerror_fixed", "signature": "def test_source_create_valueerror_fixed(self, app)"}, {"kind": "method", "line": 232, "name": "test_source_update_valueerror_fixed", "signature": "def test_source_update_valueerror_fixed(self, app)"}, {"kind": "method", "line": 264, "name": "app", "signature": "def app(self)"}, {"kind": "method", "line": 271, "name": "test_redirect_uses_public_host_not_request_host", "signature": "def test_redirect_uses_public_host_not_request_host(self, app)"}, {"kind": "method", "line": 285, "name": "test_redirect_scheme_is_https", "signature": "def test_redirect_scheme_is_https(self, app)"}, {"kind": "method", "line": 309, "name": "test_ci_yml_has_permissions", "signature": "def test_ci_yml_has_permissions(self)"}, {"kind": "method", "line": 325, "name": "test_ci_yml_permissions_is_read_all", "signature": "def test_ci_yml_permissions_is_read_all(self)"}, {"kind": "method", "line": 340, "name": "app", "signature": "def app(self)"}, {"kind": "method", "line": 347, "name": "_make_osiris_route_fixed", "signature": "def _make_osiris_route_fixed(self, app, route_path)"}, {"kind": "method", "line": 386, "name": "test_osiris_exception_returns_generic", "signature": "def test_osiris_exception_returns_generic(self, app)"}, {"kind": "method", "line": 402, "name": "test_redirect_implementation_uses_public_host", "signature": "def test_redirect_implementation_uses_public_host(self)"}, {"kind": "method", "line": 418, "name": "test_source_has_no_url_replace", "signature": "def test_source_has_no_url_replace(self)"}, {"kind": "method", "line": 436, "name": "test_js_file_exists", "signature": "def test_js_file_exists(self)"}, {"kind": "method", "line": 439, "name": "test_innerhtml_not_used_with_template_literals", "signature": "def test_innerhtml_not_used_with_template_literals(self)"}, {"doc": "showTooltipAt must sanitize html before innerHTML assignment.", "kind": "method", "line": 459, "name": "test_showtooltipat_safe", "signature": "def test_showtooltipat_safe(self)"}, {"doc": "selectNode must build DOM safely for the inspector panel.", "kind": "method", "line": 474, "name": "test_selectnode_inspector_safe", "signature": "def test_selectnode_inspector_safe(self)"}, {"kind": "method", "line": 503, "name": "test_refuses_link_local_metadata", "signature": "def test_refuses_link_local_metadata(self)"}, {"kind": "method", "line": 508, "name": "test_refuses_loopback", "signature": "def test_refuses_loopback(self)"}, {"kind": "method", "line": 512, "name": "test_refuses_disallowed_scheme", "signature": "def test_refuses_disallowed_scheme(self)"}, {"kind": "method", "line": 516, "name": "test_user_channel_url_cannot_reach_internal_host", "signature": "def test_user_channel_url_cannot_reach_internal_host(self)"}, {"kind": "method", "line": 523, "name": "test_raw_channel_url_refused_even_for_safe_host", "signature": "def test_raw_channel_url_refused_even_for_safe_host(self)"}, {"kind": "method", "line": 542, "name": "test_redirect_handler_refuses", "signature": "def test_redirect_handler_refuses(self)"}, {"kind": "method", "line": 547, "name": "test_http_post_does_not_follow_redirect", "signature": "def test_http_post_does_not_follow_redirect(self)"}, {"kind": "method", "line": 601, "name": "test_no_insert_adjacent_html_in_tooltip", "signature": "def test_no_insert_adjacent_html_in_tooltip(self)"}, {"kind": "method", "line": 618, "name": "test_sanitizer_blocks_dangerous_schemes_and_style", "signature": "def test_sanitizer_blocks_dangerous_schemes_and_style(self)"}, {"kind": "method", "line": 630, "name": "test_no_innerhtml_markdown_sink", "signature": "def test_no_innerhtml_markdown_sink(self)"}, {"kind": "method", "line": 636, "name": "test_no_html_string_round_trip", "signature": "def test_no_html_string_round_trip(self)"}, {"kind": "method", "line": 643, "name": "test_vendored_dompurify_wired_with_fallback", "signature": "def test_vendored_dompurify_wired_with_fallback(self)"}, {"kind": "method", "line": 97, "name": "api_export_test", "signature": "def api_export_test()"}, {"kind": "method", "line": 130, "name": "api_export_fixed", "signature": "def api_export_fixed()"}, {"kind": "method", "line": 191, "name": "api_delete", "signature": "def api_delete(name)"}, {"kind": "method", "line": 212, "name": "api_create", "signature": "def api_create()"}, {"kind": "method", "line": 236, "name": "api_update", "signature": "def api_update(name)"}, {"kind": "method", "line": 354, "name": "fetch_bgp", "signature": "def fetch_bgp(q)"}, {"kind": "method", "line": 357, "name": "fetch_mac", "signature": "def fetch_mac(mac)"}, {"kind": "method", "line": 360, "name": "fetch_phone", "signature": "def fetch_phone(n)"}, {"kind": "method", "line": 363, "name": "fetch_github_user", "signature": "def fetch_github_user(u)"}, {"kind": "method", "line": 366, "name": "fetch_leaks", "signature": "def fetch_leaks(e)"}, {"kind": "method", "line": 376, "name": "osiris_endpoint", "signature": "def osiris_endpoint()"}, {"kind": "class", "line": 555, "name": "_Fake302", "signature": "class _Fake302"}, {"kind": "class", "line": 564, "name": "_FakeOpener", "signature": "class _FakeOpener"}, {"kind": "method", "line": 571, "name": "_fake_build_opener", "signature": "def _fake_build_opener()"}, {"kind": "method", "line": 558, "name": "__enter__", "signature": "def __enter__(self)"}, {"kind": "method", "line": 561, "name": "__exit__", "signature": "def __exit__(self)"}, {"kind": "method", "line": 565, "name": "open", "signature": "def open(self, req, timeout)"}]}, {"doc": "BDD tests for SOCMINT sources and SocialMediaInferer.  Covers: - S1: YouTube channel lookup (happy path) - S2: YouTube channel not found - S3: YouTube without API key - S4: Twitch user lookup (happy path) - S5: Twitch user not found - S6: Twitter/X user lookup (happy path) - S7: Discord server search (happy path) - S8: SocialMediaInferer correlates across platforms - S9: SocialMediaInferer handles unknown username - S10: YouTube parser handles malformed response - S11: Twitch parser handles error responses - S12: Entity extraction from social media profiles", "id": "tests/test_socmint.py", "kind": "module", "label": "test_socmint.py", "language": "py", "sha256": "657c55fa8c894406", "symbol_count": 72, "symbols": [{"doc": "Simulated YouTube Data API v3 response for @mkbhd.", "kind": "function", "line": 40, "name": "youtube_response", "signature": "def youtube_response()"}, {"doc": "Simulated Twitch Helix API response for shroud.", "kind": "function", "line": 73, "name": "twitch_response", "signature": "def twitch_response()"}, {"doc": "Simulated Twitter/X API v2 response for elonmusk.", "kind": "function", "line": 92, "name": "twitter_response", "signature": "def twitter_response()"}, {"doc": "Simulated discords.com API search response.", "kind": "function", "line": 117, "name": "discord_response", "signature": "def discord_response()"}, {"doc": "S1: YouTube channel lookup returns structured profile.", "kind": "class", "line": 144, "name": "TestS1YouTubeHappyPath", "signature": "class TestS1YouTubeHappyPath"}, {"doc": "S2: YouTube channel not found returns error.", "kind": "class", "line": 174, "name": "TestS2YouTubeNotFound", "signature": "class TestS2YouTubeNotFound"}, {"doc": "S3: YouTube source requires a key and is excluded when --include-paid is off.", "kind": "class", "line": 193, "name": "TestS3YouTubeRequiresKey", "signature": "class TestS3YouTubeRequiresKey"}, {"doc": "S4: Twitch user lookup returns structured profile.", "kind": "class", "line": 222, "name": "TestS4TwitchHappyPath", "signature": "class TestS4TwitchHappyPath"}, {"doc": "S5: Twitch user not found returns error.", "kind": "class", "line": 250, "name": "TestS5TwitchNotFound", "signature": "class TestS5TwitchNotFound"}, {"doc": "S6: Twitter/X user lookup returns structured profile.", "kind": "class", "line": 279, "name": "TestS6TwitterHappyPath", "signature": "class TestS6TwitterHappyPath"}, {"doc": "S7: Discord server search returns structured results.", "kind": "class", "line": 319, "name": "TestS7DiscordHappyPath", "signature": "class TestS7DiscordHappyPath"}, {"doc": "S8: SocialMediaInferer correlates usernames across platforms.", "kind": "class", "line": 358, "name": "TestS8InfererCrossPlatform", "signature": "class TestS8InfererCrossPlatform"}, {"doc": "S9: SocialMediaInferer handles unknown/empty usernames.", "kind": "class", "line": 406, "name": "TestS9InfererUnknown", "signature": "class TestS9InfererUnknown"}, {"doc": "S10: YouTube parser handles malformed inputs.", "kind": "class", "line": 442, "name": "TestS10YouTubeMalformed", "signature": "class TestS10YouTubeMalformed"}, {"doc": "S11: Twitch parser handles error responses.", "kind": "class", "line": 479, "name": "TestS11TwitchErrors", "signature": "class TestS11TwitchErrors"}, {"doc": "S12: Entity extraction works on SOCMINT parser outputs.", "kind": "class", "line": 507, "name": "TestS12EntityExtraction", "signature": "class TestS12EntityExtraction"}, {"doc": "Verify the platform list method.", "kind": "class", "line": 574, "name": "TestInfererPlatformList", "signature": "class TestInfererPlatformList"}, {"doc": "Verify filtering by specific platforms.", "kind": "class", "line": 593, "name": "TestInfererResolveSpecificPlatforms", "signature": "class TestInfererResolveSpecificPlatforms"}, {"doc": "Every parser must be total: any unrecognised input returns empty, not raises.", "kind": "class", "line": 622, "name": "TestParserTotalness", "signature": "class TestParserTotalness"}, {"doc": "Given a valid YouTube channel response, the parser returns the channel_id.", "kind": "method", "line": 147, "name": "test_parser_returns_channel_id", "signature": "def test_parser_returns_channel_id(self, youtube_response)"}, {"doc": "Given a valid YouTube channel response, the parser returns subscriber_count.", "kind": "method", "line": 153, "name": "test_parser_returns_subscriber_count", "signature": "def test_parser_returns_subscriber_count(self, youtube_response)"}, {"doc": "Given a valid YouTube channel response, the parser returns title and description.", "kind": "method", "line": 160, "name": "test_parser_returns_metadata", "signature": "def test_parser_returns_metadata(self, youtube_response)"}, {"doc": "Given an empty items list, the parser returns not_found.", "kind": "method", "line": 177, "name": "test_empty_items_returns_not_found", "signature": "def test_empty_items_returns_not_found(self)"}, {"doc": "Given a response with no items key, the parser returns not_found.", "kind": "method", "line": 182, "name": "test_missing_items_returns_not_found", "signature": "def test_missing_items_returns_not_found(self)"}, {"doc": "Given the youtube_user YAML source, requires_key is True.", "kind": "method", "line": 196, "name": "test_yaml_source_has_requires_key", "signature": "def test_yaml_source_has_requires_key(self)"}, {"doc": "Given the youtube_user source, its parser is registered.", "kind": "method", "line": 209, "name": "test_parser_registered", "signature": "def test_parser_registered(self)"}, {"doc": "Given a valid Twitch response, the parser returns the user id.", "kind": "method", "line": 225, "name": "test_parser_returns_user_id", "signature": "def test_parser_returns_user_id(self, twitch_response)"}, {"doc": "Given a valid Twitch response, the parser returns display_name.", "kind": "method", "line": 231, "name": "test_parser_returns_display_name", "signature": "def test_parser_returns_display_name(self, twitch_response)"}, {"doc": "Given a valid Twitch response, the parser returns type and view_count.", "kind": "method", "line": 237, "name": "test_parser_returns_metadata", "signature": "def test_parser_returns_metadata(self, twitch_response)"}, {"doc": "Given an empty data list, the parser returns not_found.", "kind": "method", "line": 253, "name": "test_empty_data_returns_not_found", "signature": "def test_empty_data_returns_not_found(self)"}, {"doc": "Given an error response, the parser returns api_error with the detail message.", "kind": "method", "line": 258, "name": "test_error_response_returns_api_error", "signature": "def test_error_response_returns_api_error(self)"}, {"doc": "Given a response with no data key, the parser returns not_found.", "kind": "method", "line": 268, "name": "test_missing_data_returns_not_found", "signature": "def test_missing_data_returns_not_found(self)"}, {"doc": "Given a valid Twitter response, the parser returns username.", "kind": "method", "line": 282, "name": "test_parser_returns_username", "signature": "def test_parser_returns_username(self, twitter_response)"}, {"doc": "Given a valid Twitter response, the parser returns followers_count.", "kind": "method", "line": 288, "name": "test_parser_returns_followers_count", "signature": "def test_parser_returns_followers_count(self, twitter_response)"}, {"doc": "Given a valid Twitter response, the parser returns the verified flag.", "kind": "method", "line": 294, "name": "test_parser_returns_verified_flag", "signature": "def test_parser_returns_verified_flag(self, twitter_response)"}, {"doc": "Given a valid Twitter response, the parser returns location and description.", "kind": "method", "line": 299, "name": "test_parser_returns_metadata", "signature": "def test_parser_returns_metadata(self, twitter_response)"}, {"doc": "Given an error response with errors list, returns not_found.", "kind": "method", "line": 306, "name": "test_not_found_with_errors", "signature": "def test_not_found_with_errors(self)"}, {"doc": "Given a Discord server search response, the parser returns server list.", "kind": "method", "line": 322, "name": "test_parser_returns_server_list", "signature": "def test_parser_returns_server_list(self, discord_response)"}, {"doc": "Given a Discord server search response, server names are correct.", "kind": "method", "line": 328, "name": "test_parser_returns_server_names", "signature": "def test_parser_returns_server_names(self, discord_response)"}, {"doc": "Given a Discord server search response, member counts are integers.", "kind": "method", "line": 334, "name": "test_parser_returns_member_counts", "signature": "def test_parser_returns_member_counts(self, discord_response)"}, {"doc": "Given an empty response, the parser returns empty results.", "kind": "method", "line": 341, "name": "test_empty_response", "signature": "def test_empty_response(self)"}, {"doc": "Given a None response, the parser returns empty results.", "kind": "method", "line": 347, "name": "test_none_response", "signature": "def test_none_response(self)"}, {"doc": "Given username 'torvalds', the inferer returns profiles across platforms.", "kind": "method", "line": 361, "name": "test_resolve_torvalds", "signature": "def test_resolve_torvalds(self)"}, {"doc": "Given a username, Keybase appears in platform profiles.", "kind": "method", "line": 367, "name": "test_resolve_includes_keybase", "signature": "def test_resolve_includes_keybase(self)"}, {"doc": "Given a username, GitHub appears in platform profiles.", "kind": "method", "line": 373, "name": "test_resolve_includes_github", "signature": "def test_resolve_includes_github(self)"}, {"doc": "Given a common username, cross-platform confidence is above 0.5.", "kind": "method", "line": 379, "name": "test_resolve_has_high_confidence_for_populated_username", "signature": "def test_resolve_has_high_confidence_for_populated_username(self)"}, {"doc": "Given a username, linked_platforms shows the Keybase proof chain.", "kind": "method", "line": 386, "name": "test_resolve_linked_platforms_contains_keybase_note", "signature": "def test_resolve_linked_platforms_contains_keybase_note(self)"}, {"doc": "Given a username, each platform profile has a profile_url.", "kind": "method", "line": 393, "name": "test_resolve_has_profile_urls", "signature": "def test_resolve_has_profile_urls(self)"}, {"doc": "Given an empty username, returns no_matches.", "kind": "method", "line": 409, "name": "test_empty_username", "signature": "def test_empty_username(self)"}, {"doc": "Given None as username, returns no_matches.", "kind": "method", "line": 414, "name": "test_none_username", "signature": "def test_none_username(self)"}, {"doc": "Given any username, total_platforms is the count of known platforms.", "kind": "method", "line": 419, "name": "test_always_has_profile_count", "signature": "def test_always_has_profile_count(self)"}, {"doc": "Given any username, all profile URLs are valid templates.", "kind": "method", "line": 427, "name": "test_platform_urls_are_valid", "signature": "def test_platform_urls_are_valid(self)"}, {"doc": "Given None input, returns error.", "kind": "method", "line": 445, "name": "test_none_input", "signature": "def test_none_input(self)"}, {"doc": "Given a list instead of dict, returns error.", "kind": "method", "line": 450, "name": "test_list_input", "signature": "def test_list_input(self)"}, {"doc": "Given a string instead of dict, returns error.", "kind": "method", "line": 455, "name": "test_string_input", "signature": "def test_string_input(self)"}, {"doc": "Given a response without statistics, parser returns defaults gracefully.", "kind": "method", "line": 460, "name": "test_missing_statistics", "signature": "def test_missing_statistics(self)"}, {"doc": "Given a 401 error response, returns api_error.", "kind": "method", "line": 482, "name": "test_401_error", "signature": "def test_401_error(self)"}, {"doc": "Given None input, returns error.", "kind": "method", "line": 491, "name": "test_none_input", "signature": "def test_none_input(self)"}, {"doc": "Given a list instead of dict, returns error.", "kind": "method", "line": 496, "name": "test_list_input", "signature": "def test_list_input(self)"}, {"doc": "Given a YouTube channel with display_name, extract_structured yields a person.", "kind": "method", "line": 510, "name": "test_youtube_profile_extracts_person", "signature": "def test_youtube_profile_extracts_person(self)"}, {"doc": "Given a Twitter parser output, extract_structured yields person/username entities.", "kind": "method", "line": 528, "name": "test_twitter_profile_extracts_person_and_username", "signature": "def test_twitter_profile_extracts_person_and_username(self)"}, {"doc": "Given text containing social URLs, the inferer discovers cross-platform links.", "kind": "method", "line": 548, "name": "test_social_media_urls_in_text", "signature": "def test_social_media_urls_in_text(self)"}, {"doc": "Given empty text, discover_from_text returns empty.", "kind": "method", "line": 558, "name": "test_discover_empty_text", "signature": "def test_discover_empty_text(self)"}, {"doc": "Given text without URLs, discover_from_text returns empty.", "kind": "method", "line": 563, "name": "test_discover_no_urls", "signature": "def test_discover_no_urls(self)"}, {"doc": "Given the inferer, platform_list returns all known platforms.", "kind": "method", "line": 577, "name": "test_platform_list_returns_all", "signature": "def test_platform_list_returns_all(self)"}, {"doc": "Given the platform list, every entry has the core fields.", "kind": "method", "line": 583, "name": "test_platform_list_has_required_fields", "signature": "def test_platform_list_has_required_fields(self)"}, {"doc": "Given a specific platform filter, only that platform appears.", "kind": "method", "line": 596, "name": "test_resolve_single_platform", "signature": "def test_resolve_single_platform(self)"}, {"doc": "Given a list of platforms, only those platforms appear.", "kind": "method", "line": 602, "name": "test_resolve_multiple_platforms", "signature": "def test_resolve_multiple_platforms(self)"}, {"doc": "Given the inferer resolve, Twitter profiles are marked as requiring key.", "kind": "method", "line": 609, "name": "test_resolve_validates_twitter_requires_key", "signature": "def test_resolve_validates_twitter_requires_key(self)"}, {"doc": "Given None input, the parser does not raise.", "kind": "method", "line": 631, "name": "test_parser_handles_none", "signature": "def test_parser_handles_none(self, parser_fn)"}, {"doc": "Given int input, the parser does not raise.", "kind": "method", "line": 645, "name": "test_parser_handles_int", "signature": "def test_parser_handles_int(self, parser_fn)"}, {"doc": "Given string input, the parser does not raise.", "kind": "method", "line": 659, "name": "test_parser_handles_string", "signature": "def test_parser_handles_string(self, parser_fn)"}]}, {"doc": "BDD tests for estorides_core.source_health_monitoring.  These tests implement the Given-When-Then contracts declared in spec/source_health_monitoring.md. They must fail against the unwritten implementation (red step) and pass after the green step.", "id": "tests/test_source_health_monitoring.py", "kind": "module", "label": "test_source_health_monitoring.py", "language": "py", "sha256": "3cfe7b173a8decba", "symbol_count": 52, "symbols": [{"doc": "H1: high success rate, low latency -> HEALTHY.", "kind": "class", "line": 29, "name": "TestHealthySource", "signature": "class TestHealthySource"}, {"doc": "H2: low success rate -> DEGRADING.", "kind": "class", "line": 97, "name": "TestDegradingLowSuccess", "signature": "class TestDegradingLowSuccess"}, {"doc": "H3: high latency -> DEGRADING.", "kind": "class", "line": 141, "name": "TestDegradingHighLatency", "signature": "class TestDegradingHighLatency"}, {"doc": "H4: long since last seen -> STALE.", "kind": "class", "line": 186, "name": "TestStaleSource", "signature": "class TestStaleSource"}, {"doc": "H5: fetch_count < min_fetches -> UNKNOWN.", "kind": "class", "line": 217, "name": "TestUnknownSource", "signature": "class TestUnknownSource"}, {"doc": "H6: dashboard filters by status.", "kind": "class", "line": 248, "name": "TestDashboard", "signature": "class TestDashboard"}, {"doc": "H7: invalid inputs raise ValueError.", "kind": "class", "line": 348, "name": "TestValidation", "signature": "class TestValidation"}, {"doc": "H8: same input -> same output.", "kind": "class", "line": 411, "name": "TestDeterminism", "signature": "class TestDeterminism"}, {"doc": "H9 smoke: health_score is always in [0, 1].", "kind": "class", "line": 441, "name": "TestScoreBounded", "signature": "class TestScoreBounded"}, {"doc": "H10: status is always a valid enum member.", "kind": "class", "line": 472, "name": "TestStatusAlwaysValid", "signature": "class TestStatusAlwaysValid"}, {"doc": "All public types are frozen dataclasses with to_dict.", "kind": "class", "line": 492, "name": "TestDataclassContract", "signature": "class TestDataclassContract"}, {"kind": "method", "line": 32, "name": "test_healthy_status", "signature": "def test_healthy_status(self)"}, {"kind": "method", "line": 44, "name": "test_success_rate_computed", "signature": "def test_success_rate_computed(self)"}, {"kind": "method", "line": 56, "name": "test_avg_latency_computed", "signature": "def test_avg_latency_computed(self)"}, {"kind": "method", "line": 68, "name": "test_freshness_hours_computed", "signature": "def test_freshness_hours_computed(self)"}, {"kind": "method", "line": 80, "name": "test_health_score_high_band", "signature": "def test_health_score_high_band(self)"}, {"kind": "method", "line": 100, "name": "test_degrading_status", "signature": "def test_degrading_status(self)"}, {"kind": "method", "line": 112, "name": "test_success_rate_reflects_failures", "signature": "def test_success_rate_reflects_failures(self)"}, {"kind": "method", "line": 124, "name": "test_health_score_low_band", "signature": "def test_health_score_low_band(self)"}, {"kind": "method", "line": 144, "name": "test_degrading_status_for_latency", "signature": "def test_degrading_status_for_latency(self)"}, {"kind": "method", "line": 156, "name": "test_avg_latency_high", "signature": "def test_avg_latency_high(self)"}, {"kind": "method", "line": 168, "name": "test_health_score_penalised", "signature": "def test_health_score_penalised(self)"}, {"kind": "method", "line": 189, "name": "test_stale_status", "signature": "def test_stale_status(self)"}, {"kind": "method", "line": 201, "name": "test_freshness_hours_exceeds_stale", "signature": "def test_freshness_hours_exceeds_stale(self)"}, {"kind": "method", "line": 220, "name": "test_unknown_status", "signature": "def test_unknown_status(self)"}, {"kind": "method", "line": 232, "name": "test_zero_fetches_is_unknown", "signature": "def test_zero_fetches_is_unknown(self)"}, {"kind": "method", "line": 252, "name": "_healthy", "signature": "def _healthy(name)"}, {"kind": "method", "line": 263, "name": "_degrading", "signature": "def _degrading(name)"}, {"kind": "method", "line": 274, "name": "_stale", "signature": "def _stale(name)"}, {"kind": "method", "line": 285, "name": "_unknown", "signature": "def _unknown(name)"}, {"kind": "method", "line": 295, "name": "test_hot_sources_are_healthy", "signature": "def test_hot_sources_are_healthy(self)"}, {"kind": "method", "line": 306, "name": "test_degrading_includes_degrading_and_stale", "signature": "def test_degrading_includes_degrading_and_stale(self)"}, {"kind": "method", "line": 319, "name": "test_unknown_sources_separate", "signature": "def test_unknown_sources_separate(self)"}, {"kind": "method", "line": 329, "name": "test_summary_counts", "signature": "def test_summary_counts(self)"}, {"kind": "method", "line": 351, "name": "test_ok_exceeds_fetch_raises", "signature": "def test_ok_exceeds_fetch_raises(self)"}, {"kind": "method", "line": 362, "name": "test_negative_fetch_raises", "signature": "def test_negative_fetch_raises(self)"}, {"kind": "method", "line": 373, "name": "test_negative_latency_raises", "signature": "def test_negative_latency_raises(self)"}, {"kind": "method", "line": 384, "name": "test_empty_name_raises", "signature": "def test_empty_name_raises(self)"}, {"kind": "method", "line": 395, "name": "test_config_min_fetches_less_than_one_raises", "signature": "def test_config_min_fetches_less_than_one_raises(self)"}, {"kind": "method", "line": 399, "name": "test_config_stale_hours_zero_raises", "signature": "def test_config_stale_hours_zero_raises(self)"}, {"kind": "method", "line": 403, "name": "test_config_degrading_rate_out_of_range_raises", "signature": "def test_config_degrading_rate_out_of_range_raises(self)"}, {"kind": "method", "line": 414, "name": "test_compute_health_is_pure", "signature": "def test_compute_health_is_pure(self)"}, {"kind": "method", "line": 428, "name": "test_build_dashboard_is_pure", "signature": "def test_build_dashboard_is_pure(self)"}, {"kind": "method", "line": 444, "name": "test_perfect_source_scores_one", "signature": "def test_perfect_source_scores_one(self)"}, {"kind": "method", "line": 456, "name": "test_broken_source_scores_low", "signature": "def test_broken_source_scores_low(self)"}, {"kind": "method", "line": 475, "name": "test_status_is_enum", "signature": "def test_status_is_enum(self)"}, {"kind": "method", "line": 495, "name": "test_health_input_is_dataclass", "signature": "def test_health_input_is_dataclass(self)"}, {"kind": "method", "line": 498, "name": "test_health_result_is_dataclass", "signature": "def test_health_result_is_dataclass(self)"}, {"kind": "method", "line": 501, "name": "test_config_is_dataclass", "signature": "def test_config_is_dataclass(self)"}, {"kind": "method", "line": 505, "name": "test_dashboard_is_dataclass", "signature": "def test_dashboard_is_dataclass(self)"}, {"kind": "method", "line": 508, "name": "test_result_to_dict", "signature": "def test_result_to_dict(self)"}, {"kind": "method", "line": 523, "name": "test_dashboard_to_dict", "signature": "def test_dashboard_to_dict(self)"}]}, {"doc": "BDD / regression tests for SourceRegistry loading.  - SL1: a multi-document YAML file loads every document - SL2: a document holding a list of sources loads every entry - SL3: a duplicate name overwrites cleanly and category counts stay consistent - SL4: an unknown `contact` value is normalised to `active` - SL5: an undecodable file is skipped without aborting the load", "id": "tests/test_source_loader.py", "kind": "module", "label": "test_source_loader.py", "language": "py", "sha256": "eaf470d73a060f17", "symbol_count": 12, "symbols": [{"kind": "function", "line": 17, "name": "_write", "signature": "def _write(path, text)"}, {"kind": "function", "line": 22, "name": "_source", "signature": "def _source(name, category, extra)"}, {"kind": "class", "line": 32, "name": "TestSL1MultiDocument", "signature": "class TestSL1MultiDocument"}, {"kind": "class", "line": 42, "name": "TestSL2ListDocument", "signature": "class TestSL2ListDocument"}, {"kind": "class", "line": 55, "name": "TestSL3DuplicateName", "signature": "class TestSL3DuplicateName"}, {"kind": "class", "line": 70, "name": "TestSL4UnknownContact", "signature": "class TestSL4UnknownContact"}, {"kind": "class", "line": 78, "name": "TestSL5UndecodableFile", "signature": "class TestSL5UndecodableFile"}, {"kind": "method", "line": 33, "name": "test_two_documents_load", "signature": "def test_two_documents_load(self, tmp_path)"}, {"kind": "method", "line": 43, "name": "test_list_of_sources_loads", "signature": "def test_list_of_sources_loads(self, tmp_path)"}, {"kind": "method", "line": 56, "name": "test_duplicate_overwrites_counts", "signature": "def test_duplicate_overwrites_counts(self, tmp_path)"}, {"kind": "method", "line": 71, "name": "test_unknown_contact_becomes_active", "signature": "def test_unknown_contact_becomes_active(self, tmp_path)"}, {"kind": "method", "line": 79, "name": "test_bad_encoding_does_not_abort_load", "signature": "def test_bad_encoding_does_not_abort_load(self, tmp_path)"}]}, {"doc": "M2 RED tests: alias routing + phone/mac YAML backfill.", "id": "tests/test_source_routing.py", "kind": "module", "label": "test_source_routing.py", "language": "py", "sha256": "c952cdca8b071b24", "symbol_count": 6, "symbols": [{"kind": "function", "line": 13, "name": "_names", "signature": "def _names(types)"}, {"kind": "function", "line": 26, "name": "test_phone_routes_to_phone_tools", "signature": "def test_phone_routes_to_phone_tools()"}, {"kind": "function", "line": 32, "name": "test_mac_routes_to_macvendors", "signature": "def test_mac_routes_to_macvendors()"}, {"kind": "function", "line": 36, "name": "test_hash_alias_matches_concrete", "signature": "def test_hash_alias_matches_concrete()"}, {"kind": "function", "line": 43, "name": "test_url_also_matches_domain_sources", "signature": "def test_url_also_matches_domain_sources()"}, {"kind": "function", "line": 47, "name": "test_phone_yaml_tags", "signature": "def test_phone_yaml_tags()"}]}, {"doc": "BDD tests for the shared SQLite store base (spec/sqlite_store.md).  - SS1: schema applied, default path honoured - SS2: `_tx` commits - SS3: `_tx` rolls back and re-raises - SS4: `close` is idempotent - SS5: DictMixin maps a dataclass to a dict", "id": "tests/test_sqlite_store.py", "kind": "module", "label": "test_sqlite_store.py", "language": "py", "sha256": "923966a6e6033b84", "symbol_count": 14, "symbols": [{"kind": "class", "line": 20, "name": "_Store", "signature": "class _Store(SqliteStore)"}, {"kind": "class", "line": 24, "name": "TestSS1Schema", "signature": "class TestSS1Schema"}, {"kind": "class", "line": 43, "name": "TestSS2Commit", "signature": "class TestSS2Commit"}, {"kind": "class", "line": 54, "name": "TestSS3Rollback", "signature": "class TestSS3Rollback"}, {"kind": "class", "line": 67, "name": "TestSS4Close", "signature": "class TestSS4Close"}, {"kind": "class", "line": 74, "name": "TestSS5DictMixin", "signature": "class TestSS5DictMixin"}, {"kind": "method", "line": 25, "name": "test_default_path_and_schema", "signature": "def test_default_path_and_schema(self, tmp_path)"}, {"kind": "method", "line": 35, "name": "test_missing_path_raises", "signature": "def test_missing_path_raises(self)"}, {"kind": "method", "line": 44, "name": "test_tx_commits", "signature": "def test_tx_commits(self, tmp_path)"}, {"kind": "method", "line": 55, "name": "test_tx_rolls_back_and_reraises", "signature": "def test_tx_rolls_back_and_reraises(self, tmp_path)"}, {"kind": "method", "line": 68, "name": "test_close_idempotent", "signature": "def test_close_idempotent(self, tmp_path)"}, {"kind": "method", "line": 75, "name": "test_to_dict", "signature": "def test_to_dict(self)"}, {"kind": "class", "line": 36, "name": "NoPath", "signature": "class NoPath(SqliteStore)"}, {"kind": "class", "line": 77, "name": "Row", "signature": "class Row(DictMixin)"}]}, {"doc": "BDD tests for structured human-selector extraction + pivot leaf surfacing.  Ported from the standalone ``_test_people.py`` validator into pytest. Offline.", "id": "tests/test_structured_extraction.py", "kind": "module", "label": "test_structured_extraction.py", "language": "py", "sha256": "5149138ffd3eba33", "symbol_count": 8, "symbols": [{"kind": "function", "line": 32, "name": "_types", "signature": "def _types(payload)"}, {"kind": "class", "line": 39, "name": "TestExtractStructured", "signature": "class TestExtractStructured"}, {"kind": "class", "line": 58, "name": "_StubRunner", "signature": "class _StubRunner"}, {"kind": "class", "line": 67, "name": "TestPivotLeafSurfacing", "signature": "class TestPivotLeafSurfacing"}, {"kind": "method", "line": 40, "name": "test_happy_path_selectors", "signature": "def test_happy_path_selectors(self)"}, {"kind": "method", "line": 51, "name": "test_noise_rejected", "signature": "def test_noise_rejected(self)"}, {"kind": "method", "line": 59, "name": "run", "signature": "def run(self, query)"}, {"kind": "method", "line": 68, "name": "test_non_pivotable_selectors_surface_as_leaves", "signature": "def test_non_pivotable_selectors_surface_as_leaves(self)"}]}, {"doc": "ATDD + BDD tests for estorides_core.supply_chain.  Implements the Given-When-Then contracts declared in ``spec/supply_chain.md``.", "id": "tests/test_supply_chain.py", "kind": "module", "label": "test_supply_chain.py", "language": "py", "sha256": "60887b7a6e80d60b", "symbol_count": 17, "symbols": [{"kind": "class", "line": 18, "name": "TestCDNDetection", "signature": "class TestCDNDetection"}, {"kind": "class", "line": 36, "name": "TestEmailProviderDetection", "signature": "class TestEmailProviderDetection"}, {"kind": "class", "line": 66, "name": "TestNoThirdParties", "signature": "class TestNoThirdParties"}, {"kind": "class", "line": 74, "name": "TestSharedASN", "signature": "class TestSharedASN"}, {"kind": "class", "line": 95, "name": "TestNoOutboundScanning", "signature": "class TestNoOutboundScanning"}, {"kind": "class", "line": 113, "name": "TestSubsidiaryDetection", "signature": "class TestSubsidiaryDetection"}, {"kind": "class", "line": 133, "name": "TestCommonIssuerExcluded", "signature": "class TestCommonIssuerExcluded"}, {"kind": "class", "line": 148, "name": "TestRegistrarDetection", "signature": "class TestRegistrarDetection"}, {"kind": "method", "line": 19, "name": "test_cloudflare_cdn_detected", "signature": "def test_cloudflare_cdn_detected(self)"}, {"kind": "method", "line": 37, "name": "test_google_workspace_mx_detected", "signature": "def test_google_workspace_mx_detected(self)"}, {"kind": "method", "line": 51, "name": "test_microsoft_365_mx_detected", "signature": "def test_microsoft_365_mx_detected(self)"}, {"kind": "method", "line": 67, "name": "test_empty_when_self_hosted", "signature": "def test_empty_when_self_hosted(self)"}, {"kind": "method", "line": 75, "name": "test_asn_sharing_detected", "signature": "def test_asn_sharing_detected(self)"}, {"kind": "method", "line": 96, "name": "test_no_http_to_third_parties", "signature": "def test_no_http_to_third_parties(self)"}, {"kind": "method", "line": 114, "name": "test_subsidiary_relationship", "signature": "def test_subsidiary_relationship(self)"}, {"kind": "method", "line": 134, "name": "test_lets_encrypt_not_flagged", "signature": "def test_lets_encrypt_not_flagged(self)"}, {"kind": "method", "line": 149, "name": "test_godaddy_registrar", "signature": "def test_godaddy_registrar(self)"}]}, {"doc": "BDD tests for estorides_core.system_app_sources.  Kali OSINT CLI tools as first-class `system_app` sources: same aggregation pipeline as HTTP sources, new YAML origin kind. Scenarios mirror `spec/system_app_sources.md` (S1-S8).", "id": "tests/test_system_app_sources.py", "kind": "module", "label": "test_system_app_sources.py", "language": "py", "sha256": "cacbcb4e240166c9", "symbol_count": 42, "symbols": [{"kind": "function", "line": 48, "name": "_stub_runner", "signature": "def _stub_runner(exit_code, stdout, stderr, error_code, error_message, on_run)"}, {"kind": "class", "line": 79, "name": "TestS1HappyPath", "signature": "class TestS1HappyPath"}, {"kind": "class", "line": 119, "name": "TestS2MissingBinary", "signature": "class TestS2MissingBinary"}, {"kind": "class", "line": 151, "name": "TestS3Crash", "signature": "class TestS3Crash"}, {"kind": "class", "line": 171, "name": "TestS4Injection", "signature": "class TestS4Injection"}, {"kind": "class", "line": 190, "name": "TestS5JsonFileOutput", "signature": "class TestS5JsonFileOutput"}, {"kind": "class", "line": 254, "name": "TestS6Placeholders", "signature": "class TestS6Placeholders"}, {"kind": "class", "line": 272, "name": "TestS7ContactCeiling", "signature": "class TestS7ContactCeiling"}, {"kind": "class", "line": 306, "name": "TestS8Registry", "signature": "class TestS8Registry"}, {"kind": "class", "line": 408, "name": "TestRunnerErrorPassthrough", "signature": "class TestRunnerErrorPassthrough"}, {"kind": "class", "line": 433, "name": "TestS9LoopResponsiveness", "signature": "class TestS9LoopResponsiveness"}, {"kind": "method", "line": 52, "name": "stub", "signature": "def stub(binary, args)"}, {"kind": "method", "line": 80, "name": "test_execute_renders_query_and_parses_found_lines", "signature": "def test_execute_renders_query_and_parses_found_lines(self)"}, {"kind": "method", "line": 105, "name": "test_execute_returns_source_and_tool_metadata", "signature": "def test_execute_returns_source_and_tool_metadata(self)"}, {"kind": "method", "line": 120, "name": "test_execute_reports_tool_not_found", "signature": "def test_execute_reports_tool_not_found(self, monkeypatch)"}, {"kind": "method", "line": 130, "name": "test_execute_reports_missing_binary_declaration", "signature": "def test_execute_reports_missing_binary_declaration(self)"}, {"kind": "method", "line": 136, "name": "test_execute_rejects_non_allowlisted_binary", "signature": "def test_execute_rejects_non_allowlisted_binary(self, monkeypatch)"}, {"kind": "method", "line": 152, "name": "test_nonzero_exit_keeps_parsed_output", "signature": "def test_nonzero_exit_keeps_parsed_output(self)"}, {"kind": "method", "line": 172, "name": "test_metachar_arg_is_rejected_by_tool_runner", "signature": "def test_metachar_arg_is_rejected_by_tool_runner(self, monkeypatch)"}, {"kind": "method", "line": 208, "name": "test_file_output_is_parsed_and_outdir_cleaned", "signature": "def test_file_output_is_parsed_and_outdir_cleaned(self, monkeypatch)"}, {"kind": "method", "line": 235, "name": "test_stdout_json_is_parsed_when_no_file", "signature": "def test_stdout_json_is_parsed_when_no_file(self, monkeypatch)"}, {"kind": "method", "line": 255, "name": "test_query_and_outdir_substituted", "signature": "def test_query_and_outdir_substituted(self)"}, {"kind": "method", "line": 259, "name": "test_unknown_tokens_survive", "signature": "def test_unknown_tokens_survive(self)"}, {"kind": "method", "line": 263, "name": "test_non_string_arg_raises", "signature": "def test_non_string_arg_raises(self)"}, {"kind": "method", "line": 273, "name": "test_passive_only_drops_touching_tools_even_by_name", "signature": "def test_passive_only_drops_touching_tools_even_by_name(self)"}, {"kind": "method", "line": 307, "name": "_load", "signature": "def _load(self, tmp_path, yaml_text)"}, {"kind": "method", "line": 329, "name": "test_kind_and_output_format_normalise", "signature": "def test_kind_and_output_format_normalise(self, tmp_path)"}, {"kind": "method", "line": 336, "name": "test_kind_derived_from_binary_when_omitted", "signature": "def test_kind_derived_from_binary_when_omitted(self, tmp_path)"}, {"kind": "method", "line": 341, "name": "test_http_source_gets_http_kind_by_default", "signature": "def test_http_source_gets_http_kind_by_default(self, tmp_path)"}, {"kind": "method", "line": 352, "name": "test_bad_output_format_falls_back_to_text", "signature": "def test_bad_output_format_falls_back_to_text(self, tmp_path)"}, {"kind": "method", "line": 359, "name": "test_non_string_args_reset", "signature": "def test_non_string_args_reset(self, tmp_path)"}, {"kind": "method", "line": 366, "name": "test_unknown_kind_derives_from_block", "signature": "def test_unknown_kind_derives_from_block(self, tmp_path)"}, {"kind": "method", "line": 371, "name": "test_summary_exposes_kind", "signature": "def test_summary_exposes_kind(self, tmp_path)"}, {"kind": "method", "line": 376, "name": "test_real_kali_yamls_load_as_system_app", "signature": "def test_real_kali_yamls_load_as_system_app(self)"}, {"kind": "method", "line": 395, "name": "test_write_source_file_roundtrip_keeps_system_app_block", "signature": "def test_write_source_file_roundtrip_keeps_system_app_block(self, tmp_path)"}, {"kind": "method", "line": 409, "name": "test_timeout_error_is_propagated", "signature": "def test_timeout_error_is_propagated(self, monkeypatch)"}, {"kind": "method", "line": 434, "name": "test_binary_branch_runs_in_worker_thread", "signature": "def test_binary_branch_runs_in_worker_thread(self, monkeypatch)"}, {"kind": "method", "line": 83, "name": "on_run", "signature": "def on_run(binary, args)"}, {"kind": "method", "line": 214, "name": "on_run", "signature": "def on_run(binary, args)"}, {"kind": "method", "line": 414, "name": "timeout_runner", "signature": "def timeout_runner(binary, args)"}, {"kind": "method", "line": 444, "name": "slow_execute", "signature": "def slow_execute(source, query)"}, {"kind": "method", "line": 463, "name": "scenario", "signature": "def scenario()"}]}, {"id": "tests/test_target_management.py", "kind": "module", "label": "test_target_management.py", "language": "py", "sha256": "ba76a566174c9c02", "symbol_count": 86, "symbols": [{"kind": "class", "line": 21, "name": "TestS1HappyPath", "signature": "class TestS1HappyPath"}, {"kind": "class", "line": 46, "name": "TestS2AutoDetect", "signature": "class TestS2AutoDetect"}, {"kind": "class", "line": 113, "name": "TestS3InvalidValue", "signature": "class TestS3InvalidValue"}, {"kind": "class", "line": 140, "name": "TestS4UnknownType", "signature": "class TestS4UnknownType"}, {"kind": "class", "line": 156, "name": "TestS5EmptyValue", "signature": "class TestS5EmptyValue"}, {"kind": "class", "line": 174, "name": "TestS6CaseStoreUnavailable", "signature": "class TestS6CaseStoreUnavailable"}, {"kind": "class", "line": 186, "name": "TestS7Batch", "signature": "class TestS7Batch"}, {"kind": "class", "line": 211, "name": "TestS8MaxBatch", "signature": "class TestS8MaxBatch"}, {"kind": "class", "line": 228, "name": "TestS9XSS", "signature": "class TestS9XSS"}, {"kind": "class", "line": 246, "name": "TestS10Determinism", "signature": "class TestS10Determinism"}, {"kind": "class", "line": 266, "name": "TestValidateType", "signature": "class TestValidateType"}, {"kind": "class", "line": 280, "name": "TestValidateValue", "signature": "class TestValidateValue"}, {"kind": "class", "line": 333, "name": "TestMakeTargetId", "signature": "class TestMakeTargetId"}, {"kind": "class", "line": 344, "name": "TestAutoDetectType", "signature": "class TestAutoDetectType"}, {"kind": "class", "line": 367, "name": "TestBatchResultSerialization", "signature": "class TestBatchResultSerialization"}, {"kind": "class", "line": 379, "name": "TestTargetResultSerialization", "signature": "class TestTargetResultSerialization"}, {"kind": "class", "line": 410, "name": "TestCsvImport", "signature": "class TestCsvImport"}, {"kind": "method", "line": 22, "name": "test_valid_target_returns_result", "signature": "def test_valid_target_returns_result(self)"}, {"kind": "method", "line": 30, "name": "test_has_case_id", "signature": "def test_has_case_id(self)"}, {"kind": "method", "line": 35, "name": "test_id_deterministic", "signature": "def test_id_deterministic(self)"}, {"kind": "method", "line": 47, "name": "test_ipv4_auto", "signature": "def test_ipv4_auto(self)"}, {"kind": "method", "line": 52, "name": "test_ipv6_auto", "signature": "def test_ipv6_auto(self)"}, {"kind": "method", "line": 57, "name": "test_email_auto", "signature": "def test_email_auto(self)"}, {"kind": "method", "line": 62, "name": "test_cve_auto", "signature": "def test_cve_auto(self)"}, {"kind": "method", "line": 67, "name": "test_btc_auto", "signature": "def test_btc_auto(self)"}, {"kind": "method", "line": 72, "name": "test_eth_auto", "signature": "def test_eth_auto(self)"}, {"kind": "method", "line": 77, "name": "test_domain_auto", "signature": "def test_domain_auto(self)"}, {"kind": "method", "line": 82, "name": "test_username_fallback", "signature": "def test_username_fallback(self)"}, {"kind": "method", "line": 87, "name": "test_phone_auto", "signature": "def test_phone_auto(self)"}, {"kind": "method", "line": 92, "name": "test_asn_auto", "signature": "def test_asn_auto(self)"}, {"kind": "method", "line": 97, "name": "test_md5_auto", "signature": "def test_md5_auto(self)"}, {"kind": "method", "line": 102, "name": "test_sha256_auto", "signature": "def test_sha256_auto(self)"}, {"kind": "method", "line": 114, "name": "test_invalid_email", "signature": "def test_invalid_email(self)"}, {"kind": "method", "line": 119, "name": "test_invalid_ipv4", "signature": "def test_invalid_ipv4(self)"}, {"kind": "method", "line": 123, "name": "test_invalid_domain_script", "signature": "def test_invalid_domain_script(self)"}, {"kind": "method", "line": 127, "name": "test_invalid_url", "signature": "def test_invalid_url(self)"}, {"kind": "method", "line": 131, "name": "test_invalid_phone", "signature": "def test_invalid_phone(self)"}, {"kind": "method", "line": 141, "name": "test_unknown_type", "signature": "def test_unknown_type(self)"}, {"kind": "method", "line": 146, "name": "test_unknown_type_via_manager", "signature": "def test_unknown_type_via_manager(self)"}, {"kind": "method", "line": 157, "name": "test_empty_raises", "signature": "def test_empty_raises(self)"}, {"kind": "method", "line": 161, "name": "test_whitespace_raises", "signature": "def test_whitespace_raises(self)"}, {"kind": "method", "line": 165, "name": "test_validate_value_empty", "signature": "def test_validate_value_empty(self)"}, {"kind": "method", "line": 175, "name": "test_ephemeral_no_case_store", "signature": "def test_ephemeral_no_case_store(self)"}, {"kind": "method", "line": 187, "name": "test_mixed_batch", "signature": "def test_mixed_batch(self)"}, {"kind": "method", "line": 201, "name": "test_simple_lines_no_type", "signature": "def test_simple_lines_no_type(self)"}, {"kind": "method", "line": 212, "name": "test_exceeds_max", "signature": "def test_exceeds_max(self)"}, {"kind": "method", "line": 217, "name": "test_at_max", "signature": "def test_at_max(self)"}, {"kind": "method", "line": 229, "name": "test_script_in_domain_rejected", "signature": "def test_script_in_domain_rejected(self)"}, {"kind": "method", "line": 233, "name": "test_onclick_in_domain_rejected", "signature": "def test_onclick_in_domain_rejected(self)"}, {"kind": "method", "line": 237, "name": "test_sql_injection_in_email_rejected", "signature": "def test_sql_injection_in_email_rejected(self)"}, {"kind": "method", "line": 247, "name": "test_same_id", "signature": "def test_same_id(self)"}, {"kind": "method", "line": 255, "name": "test_same_id_normalised", "signature": "def test_same_id_normalised(self)"}, {"kind": "method", "line": 267, "name": "test_valid_types_pass", "signature": "def test_valid_types_pass(self)"}, {"kind": "method", "line": 272, "name": "test_auto_passes", "signature": "def test_auto_passes(self)"}, {"kind": "method", "line": 275, "name": "test_invalid_fails", "signature": "def test_invalid_fails(self)"}, {"kind": "method", "line": 281, "name": "test_domain_valid", "signature": "def test_domain_valid(self)"}, {"kind": "method", "line": 285, "name": "test_domain_invalid", "signature": "def test_domain_invalid(self)"}, {"kind": "method", "line": 289, "name": "test_ipv4_valid", "signature": "def test_ipv4_valid(self)"}, {"kind": "method", "line": 293, "name": "test_ipv4_invalid_octet", "signature": "def test_ipv4_invalid_octet(self)"}, {"kind": "method", "line": 297, "name": "test_email_valid", "signature": "def test_email_valid(self)"}, {"kind": "method", "line": 301, "name": "test_url_only_http_https", "signature": "def test_url_only_http_https(self)"}, {"kind": "method", "line": 306, "name": "test_username_no_regex", "signature": "def test_username_no_regex(self)"}, {"kind": "method", "line": 310, "name": "test_cve_valid", "signature": "def test_cve_valid(self)"}, {"kind": "method", "line": 313, "name": "test_btc_valid", "signature": "def test_btc_valid(self)"}, {"kind": "method", "line": 316, "name": "test_eth_valid", "signature": "def test_eth_valid(self)"}, {"kind": "method", "line": 319, "name": "test_phone_valid", "signature": "def test_phone_valid(self)"}, {"kind": "method", "line": 322, "name": "test_asn_valid", "signature": "def test_asn_valid(self)"}, {"kind": "method", "line": 325, "name": "test_md5_valid", "signature": "def test_md5_valid(self)"}, {"kind": "method", "line": 328, "name": "test_sha256_valid", "signature": "def test_sha256_valid(self)"}, {"kind": "method", "line": 334, "name": "test_length", "signature": "def test_length(self)"}, {"kind": "method", "line": 337, "name": "test_deterministic", "signature": "def test_deterministic(self)"}, {"kind": "method", "line": 340, "name": "test_case_sensitive_normalised", "signature": "def test_case_sensitive_normalised(self)"}, {"kind": "method", "line": 345, "name": "test_ipv4", "signature": "def test_ipv4(self)"}, {"kind": "method", "line": 348, "name": "test_ipv6", "signature": "def test_ipv6(self)"}, {"kind": "method", "line": 351, "name": "test_email", "signature": "def test_email(self)"}, {"kind": "method", "line": 354, "name": "test_domain", "signature": "def test_domain(self)"}, {"kind": "method", "line": 357, "name": "test_url", "signature": "def test_url(self)"}, {"kind": "method", "line": 360, "name": "test_cve", "signature": "def test_cve(self)"}, {"kind": "method", "line": 363, "name": "test_username_fallback", "signature": "def test_username_fallback(self)"}, {"kind": "method", "line": 368, "name": "test_to_dict", "signature": "def test_to_dict(self)"}, {"kind": "method", "line": 380, "name": "test_to_dict", "signature": "def test_to_dict(self)"}, {"kind": "method", "line": 397, "name": "test_to_dict_invalid", "signature": "def test_to_dict_invalid(self)"}, {"kind": "method", "line": 411, "name": "test_basic_csv", "signature": "def test_basic_csv(self)"}, {"kind": "method", "line": 418, "name": "test_csv_invalid", "signature": "def test_csv_invalid(self)"}, {"kind": "method", "line": 425, "name": "test_csv_max_batch", "signature": "def test_csv_max_batch(self)"}, {"kind": "method", "line": 431, "name": "test_csv_exceeds_max", "signature": "def test_csv_exceeds_max(self)"}]}, {"doc": "ATDD + BDD tests for estorides_core.target_scoring.  Implements the Given-When-Then contracts declared in ``spec/target_scoring.md``.", "id": "tests/test_target_scoring.py", "kind": "module", "label": "test_target_scoring.py", "language": "py", "sha256": "249d1a920c0061fc", "symbol_count": 18, "symbols": [{"kind": "function", "line": 17, "name": "_make_target", "signature": "def _make_target(domain, surface, soft, jewel, lateral)"}, {"kind": "class", "line": 36, "name": "TestCriticalTarget", "signature": "class TestCriticalTarget"}, {"kind": "class", "line": 44, "name": "TestNoFindingsNoise", "signature": "class TestNoFindingsNoise"}, {"kind": "class", "line": 52, "name": "TestMixedScoring", "signature": "class TestMixedScoring"}, {"kind": "class", "line": 70, "name": "TestConfigurableWeights", "signature": "class TestConfigurableWeights"}, {"kind": "class", "line": 82, "name": "TestCrownJewelDetection", "signature": "class TestCrownJewelDetection"}, {"kind": "class", "line": 94, "name": "TestPartialData", "signature": "class TestPartialData"}, {"kind": "class", "line": 101, "name": "TestLateralPotential", "signature": "class TestLateralPotential"}, {"kind": "class", "line": 108, "name": "TestNoCredentialLeakage", "signature": "class TestNoCredentialLeakage"}, {"kind": "method", "line": 37, "name": "test_open_bucket_and_old_nginx_ranked_critical", "signature": "def test_open_bucket_and_old_nginx_ranked_critical(self)"}, {"kind": "method", "line": 45, "name": "test_no_findings_is_noise", "signature": "def test_no_findings_is_noise(self)"}, {"kind": "method", "line": 53, "name": "test_5_targets_various_tiers", "signature": "def test_5_targets_various_tiers(self)"}, {"kind": "method", "line": 71, "name": "test_custom_weights_change_score", "signature": "def test_custom_weights_change_score(self)"}, {"kind": "method", "line": 83, "name": "test_jenkins_jira_vpn_get_high_jewel_score", "signature": "def test_jenkins_jira_vpn_get_high_jewel_score(self)"}, {"kind": "method", "line": 88, "name": "test_blog_low_jewel_score", "signature": "def test_blog_low_jewel_score(self)"}, {"kind": "method", "line": 95, "name": "test_partial_data_lower_confidence", "signature": "def test_partial_data_lower_confidence(self)"}, {"kind": "method", "line": 102, "name": "test_password_reuse_increases_lateral", "signature": "def test_password_reuse_increases_lateral(self)"}, {"kind": "method", "line": 109, "name": "test_serialised_output_no_credentials", "signature": "def test_serialised_output_no_credentials(self)"}]}, {"doc": "ATDD + BDD tests for estorides_core.tech_fingerprint.  Implements the Given-When-Then contracts declared in ``spec/tech_fingerprint.md``. Property-based invariants live in ``tests/properties/test_tech_fingerprint_properties.py``.", "id": "tests/test_tech_fingerprint.py", "kind": "module", "label": "test_tech_fingerprint.py", "language": "py", "sha256": "40714d67e07e5085", "symbol_count": 16, "symbols": [{"kind": "class", "line": 15, "name": "TestHappyPathFullStack", "signature": "class TestHappyPathFullStack"}, {"kind": "class", "line": 31, "name": "TestEmptyResponse", "signature": "class TestEmptyResponse"}, {"kind": "class", "line": 39, "name": "TestMalformedVersion", "signature": "class TestMalformedVersion"}, {"kind": "class", "line": 49, "name": "TestNoScriptInjection", "signature": "class TestNoScriptInjection"}, {"kind": "class", "line": 58, "name": "TestWafDetection", "signature": "class TestWafDetection"}, {"kind": "class", "line": 69, "name": "TestCmsDetection", "signature": "class TestCmsDetection"}, {"kind": "class", "line": 80, "name": "TestDeduplication", "signature": "class TestDeduplication"}, {"kind": "class", "line": 90, "name": "TestInputSizeBound", "signature": "class TestInputSizeBound"}, {"kind": "method", "line": 16, "name": "test_detects_nginx_php_jquery", "signature": "def test_detects_nginx_php_jquery(self)"}, {"kind": "method", "line": 32, "name": "test_returns_empty_on_no_input", "signature": "def test_returns_empty_on_no_input(self)"}, {"kind": "method", "line": 40, "name": "test_handles_binary_garbage_in_version", "signature": "def test_handles_binary_garbage_in_version(self)"}, {"kind": "method", "line": 50, "name": "test_does_not_parse_script_as_tech", "signature": "def test_does_not_parse_script_as_tech(self)"}, {"kind": "method", "line": 59, "name": "test_detects_cloudflare_from_headers", "signature": "def test_detects_cloudflare_from_headers(self)"}, {"kind": "method", "line": 70, "name": "test_detects_wordpress_from_meta", "signature": "def test_detects_wordpress_from_meta(self)"}, {"kind": "method", "line": 81, "name": "test_same_tech_from_multiple_signals_appears_once", "signature": "def test_same_tech_from_multiple_signals_appears_once(self)"}, {"kind": "method", "line": 91, "name": "test_only_processes_first_100kb", "signature": "def test_only_processes_first_100kb(self)"}]}, {"doc": "M4 RED tests: tool doctor.", "id": "tests/test_tool_doctor.py", "kind": "module", "label": "test_tool_doctor.py", "language": "py", "sha256": "b6763c1456380723", "symbol_count": 4, "symbols": [{"kind": "function", "line": 5, "name": "test_doctor_covers_system_app_binaries", "signature": "def test_doctor_covers_system_app_binaries()"}, {"kind": "function", "line": 16, "name": "test_doctor_flags_path_binary", "signature": "def test_doctor_flags_path_binary()"}, {"kind": "function", "line": 25, "name": "test_doctor_installable_when_missing_with_recipe", "signature": "def test_doctor_installable_when_missing_with_recipe()"}, {"kind": "function", "line": 35, "name": "test_doctor_summary_consistent", "signature": "def test_doctor_summary_consistent()"}]}, {"doc": "Tests for estorides_core.tool_install (lazyaddon-style tool installation).  Covers recipe loading, elevation choice (run0 preferred over sudo, none when root), apt vs git install methods, and the TOOL_NOT_FOUND install flow. All subprocess calls are mocked so no network or root access is required.", "id": "tests/test_tool_install.py", "kind": "module", "label": "test_tool_install.py", "language": "py", "sha256": "386f23814dc93bfa", "symbol_count": 28, "symbols": [{"doc": "Make every _run call a no-op success so tests never touch the network.", "kind": "function", "line": 17, "name": "no_network", "signature": "def no_network()"}, {"kind": "class", "line": 24, "name": "TestRecipes", "signature": "class TestRecipes"}, {"kind": "class", "line": 44, "name": "TestElevation", "signature": "class TestElevation"}, {"kind": "class", "line": 61, "name": "TestInstallFlow", "signature": "class TestInstallFlow"}, {"doc": "CodeQL #48/#49/#52: uncontrolled recipe names must never escape\nTOOL_RECIPES_DIR; clone targets must stay inside TOOLS_DIR; binary\nnames are validated before any PATH probe. Only directory-listed\nrecipe stems ever reach the filesystem.", "kind": "class", "line": 106, "name": "TestPathTraversal", "signature": "class TestPathTraversal"}, {"kind": "method", "line": 25, "name": "test_recipe_available_for_known_tool", "signature": "def test_recipe_available_for_known_tool(self)"}, {"kind": "method", "line": 28, "name": "test_recipe_unavailable_for_unknown_tool", "signature": "def test_recipe_unavailable_for_unknown_tool(self)"}, {"kind": "method", "line": 31, "name": "test_load_apt_recipe", "signature": "def test_load_apt_recipe(self)"}, {"kind": "method", "line": 35, "name": "test_load_git_recipe", "signature": "def test_load_git_recipe(self)"}, {"kind": "method", "line": 39, "name": "test_list_recipes_is_nonempty", "signature": "def test_list_recipes_is_nonempty(self)"}, {"kind": "method", "line": 45, "name": "test_run0_preferred_over_sudo", "signature": "def test_run0_preferred_over_sudo(self)"}, {"kind": "method", "line": 50, "name": "test_sudo_fallback_when_no_run0", "signature": "def test_sudo_fallback_when_no_run0(self)"}, {"kind": "method", "line": 55, "name": "test_no_elevation_when_root", "signature": "def test_no_elevation_when_root(self)"}, {"kind": "method", "line": 62, "name": "test_already_installed_noop", "signature": "def test_already_installed_noop(self, no_network)"}, {"kind": "method", "line": 67, "name": "test_not_in_allowlist_rejected", "signature": "def test_not_in_allowlist_rejected(self)"}, {"kind": "method", "line": 72, "name": "test_no_recipe_rejected", "signature": "def test_no_recipe_rejected(self)"}, {"kind": "method", "line": 78, "name": "test_apt_install_success", "signature": "def test_apt_install_success(self)"}, {"kind": "method", "line": 89, "name": "test_verify_fails_after_install", "signature": "def test_verify_fails_after_install(self, no_network)"}, {"kind": "method", "line": 95, "name": "test_git_install_runs_clone", "signature": "def test_git_install_runs_clone(self)"}, {"kind": "method", "line": 112, "name": "test_unknown_valid_name_never_touches_filesystem", "signature": "def test_unknown_valid_name_never_touches_filesystem(self)"}, {"kind": "method", "line": 117, "name": "test_install_tool_unknown_recipe_without_fs_probe", "signature": "def test_install_tool_unknown_recipe_without_fs_probe(self)"}, {"kind": "method", "line": 127, "name": "test_load_recipe_rejects_traversal", "signature": "def test_load_recipe_rejects_traversal(self, evil)"}, {"kind": "method", "line": 130, "name": "test_recipe_path_rejects_traversal", "signature": "def test_recipe_path_rejects_traversal(self)"}, {"kind": "method", "line": 134, "name": "test_recipe_available_rejects_traversal", "signature": "def test_recipe_available_rejects_traversal(self)"}, {"kind": "method", "line": 137, "name": "test_git_clone_refuses_escape", "signature": "def test_git_clone_refuses_escape(self)"}, {"kind": "method", "line": 149, "name": "test_install_tool_rejects_bad_binary_without_path_probe", "signature": "def test_install_tool_rejects_bad_binary_without_path_probe(self, evil)"}, {"kind": "method", "line": 159, "name": "test_install_tool_allowlist_checked_before_shortcut", "signature": "def test_install_tool_allowlist_checked_before_shortcut(self)"}, {"kind": "method", "line": 165, "name": "test_tool_available_rejects_bad_name", "signature": "def test_tool_available_rejects_bad_name(self)"}]}, {"doc": "ATDD + BDD tests for estorides_core.tool_runner.  Implements the Given-When-Then contracts declared in ``spec/tool_runner.md``. Property-based invariants live in ``tests/properties/test_tool_runner_properties.py``.", "id": "tests/test_tool_runner.py", "kind": "module", "label": "test_tool_runner.py", "language": "py", "sha256": "10deadbd2b021c83", "symbol_count": 29, "symbols": [{"kind": "class", "line": 20, "name": "TestHappyPathNmap", "signature": "class TestHappyPathNmap"}, {"kind": "class", "line": 29, "name": "TestInjectionBlocked", "signature": "class TestInjectionBlocked"}, {"kind": "class", "line": 81, "name": "TestTimeout", "signature": "class TestTimeout"}, {"kind": "class", "line": 96, "name": "TestToolNotFound", "signature": "class TestToolNotFound"}, {"kind": "class", "line": 117, "name": "TestToolNotAllowed", "signature": "class TestToolNotAllowed"}, {"kind": "class", "line": 129, "name": "TestNoArgs", "signature": "class TestNoArgs"}, {"kind": "class", "line": 141, "name": "TestTargetValidation", "signature": "class TestTargetValidation"}, {"kind": "class", "line": 147, "name": "TestNonZeroExit", "signature": "class TestNonZeroExit"}, {"kind": "class", "line": 156, "name": "TestOutputSha1", "signature": "class TestOutputSha1"}, {"kind": "class", "line": 164, "name": "TestConfidenceRange", "signature": "class TestConfidenceRange"}, {"kind": "class", "line": 171, "name": "TestOutputTruncation", "signature": "class TestOutputTruncation"}, {"kind": "class", "line": 184, "name": "TestErrorResultFields", "signature": "class TestErrorResultFields"}, {"kind": "method", "line": 21, "name": "test_run_tool_returns_result", "signature": "def test_run_tool_returns_result(self)"}, {"kind": "method", "line": 30, "name": "test_semicolon_in_arg_rejected", "signature": "def test_semicolon_in_arg_rejected(self)"}, {"kind": "method", "line": 40, "name": "test_pipe_in_arg_rejected", "signature": "def test_pipe_in_arg_rejected(self)"}, {"kind": "method", "line": 50, "name": "test_backtick_in_arg_rejected", "signature": "def test_backtick_in_arg_rejected(self)"}, {"kind": "method", "line": 60, "name": "test_dollar_paren_in_arg_rejected", "signature": "def test_dollar_paren_in_arg_rejected(self)"}, {"kind": "method", "line": 70, "name": "test_newline_in_arg_rejected", "signature": "def test_newline_in_arg_rejected(self)"}, {"kind": "method", "line": 82, "name": "test_tool_that_exceeds_timeout_returns_tool_timeout", "signature": "def test_tool_that_exceeds_timeout_returns_tool_timeout(self)"}, {"kind": "method", "line": 97, "name": "test_tool_not_on_filesystem_returns_error", "signature": "def test_tool_not_on_filesystem_returns_error(self)"}, {"kind": "method", "line": 118, "name": "test_disallowed_tool_rejected", "signature": "def test_disallowed_tool_rejected(self)"}, {"kind": "method", "line": 130, "name": "test_empty_args_returns_error", "signature": "def test_empty_args_returns_error(self)"}, {"kind": "method", "line": 142, "name": "test_control_char_rejected_by_validation", "signature": "def test_control_char_rejected_by_validation(self)"}, {"kind": "method", "line": 148, "name": "test_nmap_version_query_succeeds", "signature": "def test_nmap_version_query_succeeds(self)"}, {"kind": "method", "line": 157, "name": "test_sha1_is_valid_hex", "signature": "def test_sha1_is_valid_hex(self)"}, {"kind": "method", "line": 165, "name": "test_confidence_in_bounds", "signature": "def test_confidence_in_bounds(self)"}, {"kind": "method", "line": 172, "name": "test_truncated_flag_when_output_exceeds_limit", "signature": "def test_truncated_flag_when_output_exceeds_limit(self)"}, {"kind": "method", "line": 185, "name": "test_error_result_has_fields", "signature": "def test_error_result_has_fields(self)"}, {"kind": "method", "line": 206, "name": "test_injection_error_has_fields", "signature": "def test_injection_error_has_fields(self)"}]}, {"doc": "BDD tests for spec/transforms.md — Maltego-style pivoting.  S1 happy, S2 edge vacío, S3 error id, S4 runner-roto, S5 metadata rica, S6 validación input + stream shape, S7 yaml sin código. Rojo hasta que transforms.py gane input_types/output_types/cost + límites.", "id": "tests/test_transforms.py", "kind": "module", "label": "test_transforms.py", "language": "py", "sha256": "53e355cdfd231f30", "symbol_count": 12, "symbols": [{"kind": "function", "line": 10, "name": "_mock_resolver", "signature": "def _mock_resolver(monkeypatch, nodes, links, root_id)"}, {"kind": "function", "line": 20, "name": "test_s1_ip_to_bgp_happy", "signature": "def test_s1_ip_to_bgp_happy(monkeypatch)"}, {"kind": "function", "line": 35, "name": "test_s2_empty_osiris_no_raise", "signature": "def test_s2_empty_osiris_no_raise(monkeypatch)"}, {"kind": "function", "line": 47, "name": "test_s3_unknown_transform_id", "signature": "def test_s3_unknown_transform_id()"}, {"kind": "function", "line": 53, "name": "test_s4_runner_exception_fail_closed", "signature": "def test_s4_runner_exception_fail_closed()"}, {"kind": "function", "line": 67, "name": "test_s5_rich_metadata_sorted", "signature": "def test_s5_rich_metadata_sorted()"}, {"kind": "function", "line": 80, "name": "test_s6_input_limits_and_stream_shape", "signature": "def test_s6_input_limits_and_stream_shape()"}, {"kind": "function", "line": 96, "name": "test_s7_yaml_transform_no_code", "signature": "def test_s7_yaml_transform_no_code(monkeypatch, tmp_path)"}, {"kind": "function", "line": 130, "name": "_repo_transforms_dir", "signature": "def _repo_transforms_dir()"}, {"doc": "Every transforms/*.yaml ships full metadata; static pivots run\noffline with zero '{query}' leftovers, even inside properties.", "kind": "function", "line": 135, "name": "test_s8_yaml_catalog_complete_and_substituted", "signature": "def test_s8_yaml_catalog_complete_and_substituted()"}, {"kind": "class", "line": 13, "name": "Fake", "signature": "class Fake"}, {"kind": "method", "line": 14, "name": "resolve", "signature": "def resolve(self, t, v)"}]}, {"doc": "BDD tests for the ui_professional module (spec/ui_professional.md).  Each scenario maps to a Given-When-From from the spec. We test the JS functions via rendering the template and asserting DOM structure, plus direct Python-side logic for the tier+loading helpers.", "id": "tests/test_ui_professional.py", "kind": "module", "label": "test_ui_professional.py", "language": "py", "sha256": "2eb0abf5bab654ed", "symbol_count": 39, "symbols": [{"kind": "function", "line": 27, "name": "_render_index", "signature": "def _render_index()"}, {"kind": "function", "line": 39, "name": "_simulate_tiered_data", "signature": "def _simulate_tiered_data()"}, {"doc": "S1 -- Loading indicator elements exist in the DOM.", "kind": "class", "line": 64, "name": "TestS1LoadingAnimation", "signature": "class TestS1LoadingAnimation"}, {"doc": "S2 -- CRITICAL tier renders expanded with correct badge count.", "kind": "class", "line": 93, "name": "TestS2CriticalExpanded", "signature": "class TestS2CriticalExpanded"}, {"kind": "class", "line": 119, "name": "TestS3NoiseCollapsed", "signature": "class TestS3NoiseCollapsed"}, {"kind": "class", "line": 143, "name": "TestS4ToggleExpandCollapse", "signature": "class TestS4ToggleExpandCollapse"}, {"kind": "class", "line": 157, "name": "TestS5FallbackFlatView", "signature": "class TestS5FallbackFlatView"}, {"kind": "class", "line": 175, "name": "TestS6LoadingTimeout", "signature": "class TestS6LoadingTimeout"}, {"kind": "class", "line": 184, "name": "TestS7HoverEffect", "signature": "class TestS7HoverEffect"}, {"kind": "class", "line": 197, "name": "TestS8FadeInTransition", "signature": "class TestS8FadeInTransition"}, {"kind": "class", "line": 211, "name": "TestS9SecurityCSP", "signature": "class TestS9SecurityCSP"}, {"kind": "class", "line": 244, "name": "TestS10XSSSafe", "signature": "class TestS10XSSSafe"}, {"kind": "class", "line": 262, "name": "TestIntegrationTierPipeline", "signature": "class TestIntegrationTierPipeline"}, {"kind": "method", "line": 67, "name": "test_loading_elements_exist", "signature": "def test_loading_elements_exist(self)"}, {"kind": "method", "line": 74, "name": "test_loading_css_defined", "signature": "def test_loading_css_defined(self)"}, {"kind": "method", "line": 83, "name": "test_js_show_working_indicator_exists", "signature": "def test_js_show_working_indicator_exists(self)"}, {"kind": "method", "line": 96, "name": "test_critical_tier_data", "signature": "def test_critical_tier_data(self)"}, {"kind": "method", "line": 105, "name": "test_critical_css_classes_exist", "signature": "def test_critical_css_classes_exist(self)"}, {"kind": "method", "line": 120, "name": "test_noise_tier_data", "signature": "def test_noise_tier_data(self)"}, {"kind": "method", "line": 127, "name": "test_noise_css_classes_exist", "signature": "def test_noise_css_classes_exist(self)"}, {"kind": "method", "line": 134, "name": "test_js_toggle_function_exists", "signature": "def test_js_toggle_function_exists(self)"}, {"kind": "method", "line": 144, "name": "test_aria_attributes_in_js", "signature": "def test_aria_attributes_in_js(self)"}, {"kind": "method", "line": 149, "name": "test_toggle_uses_role_button", "signature": "def test_toggle_uses_role_button(self)"}, {"kind": "method", "line": 158, "name": "test_js_fallback_logic", "signature": "def test_js_fallback_logic(self)"}, {"kind": "method", "line": 163, "name": "test_tiers_missing_returns_empty", "signature": "def test_tiers_missing_returns_empty(self)"}, {"kind": "method", "line": 176, "name": "test_show_toast_exists", "signature": "def test_show_toast_exists(self)"}, {"kind": "method", "line": 185, "name": "test_tier_group_hover_css", "signature": "def test_tier_group_hover_css(self)"}, {"kind": "method", "line": 189, "name": "test_transition_on_tier_group", "signature": "def test_transition_on_tier_group(self)"}, {"kind": "method", "line": 198, "name": "test_fade_in_css_exists", "signature": "def test_fade_in_css_exists(self)"}, {"kind": "method", "line": 203, "name": "test_results_use_fade_in", "signature": "def test_results_use_fade_in(self)"}, {"kind": "method", "line": 212, "name": "test_no_inline_style_in_tier_badge", "signature": "def test_no_inline_style_in_tier_badge(self)"}, {"kind": "method", "line": 222, "name": "test_no_inline_style_in_template", "signature": "def test_no_inline_style_in_template(self)"}, {"kind": "method", "line": 233, "name": "test_no_onclick_attributes", "signature": "def test_no_onclick_attributes(self)"}, {"kind": "method", "line": 245, "name": "test_escape_html_function_exists", "signature": "def test_escape_html_function_exists(self)"}, {"kind": "method", "line": 249, "name": "test_escape_html_properly_defined", "signature": "def test_escape_html_properly_defined(self)"}, {"kind": "method", "line": 254, "name": "test_tier_label_uses_text_content", "signature": "def test_tier_label_uses_text_content(self)"}, {"kind": "method", "line": 263, "name": "test_tier_summary_accuracy", "signature": "def test_tier_summary_accuracy(self)"}, {"kind": "method", "line": 268, "name": "test_every_group_has_required_fields", "signature": "def test_every_group_has_required_fields(self)"}, {"kind": "method", "line": 280, "name": "test_scores_are_normalised", "signature": "def test_scores_are_normalised(self)"}]}, {"doc": "Regression tests for the `hidden` attribute contract and output escaping.  `estorides_ui.css` enforces `[hidden] { display: none !important }`, so an element carrying the HTML5 `hidden` attribute can only be revealed by clearing the attribute. Toggling inline `style.display` alone leaves it permanently invisible. These tests pin the JS to the correct pattern.  Also pins that remote-sourced fields interpolated into `innerHTML` are escaped (XSS defence for the fusion entity detail + entity list).", "id": "tests/test_ui_visibility.py", "kind": "module", "label": "test_ui_visibility.py", "language": "py", "sha256": "59a719a03cf898c0", "symbol_count": 9, "symbols": [{"kind": "function", "line": 21, "name": "_js", "signature": "def _js()"}, {"kind": "class", "line": 25, "name": "TestHiddenContract", "signature": "class TestHiddenContract"}, {"kind": "class", "line": 54, "name": "TestOutputEscaping", "signature": "class TestOutputEscaping"}, {"kind": "method", "line": 26, "name": "test_css_enforces_hidden", "signature": "def test_css_enforces_hidden(self)"}, {"kind": "method", "line": 29, "name": "test_setvisible_clears_hidden_attribute", "signature": "def test_setvisible_clears_hidden_attribute(self)"}, {"doc": "The old buggy patterns that could never reveal a hidden element.", "kind": "method", "line": 34, "name": "test_no_inline_display_reveal_of_hidden_overlays", "signature": "def test_no_inline_display_reveal_of_hidden_overlays(self)"}, {"kind": "method", "line": 47, "name": "test_tooltip_and_menu_use_setvisible", "signature": "def test_tooltip_and_menu_use_setvisible(self)"}, {"kind": "method", "line": 55, "name": "test_entity_list_escapes_type_and_source", "signature": "def test_entity_list_escapes_type_and_source(self)"}, {"kind": "method", "line": 60, "name": "test_fusion_detail_escapes_intel_level_and_lists", "signature": "def test_fusion_detail_escapes_intel_level_and_lists(self)"}]}, {"doc": "ATDD + BDD tests for estorides_core.vuln_correlation.  Implements the Given-When-Then contracts declared in ``spec/vuln_correlation.md``.", "id": "tests/test_vuln_correlation.py", "kind": "module", "label": "test_vuln_correlation.py", "language": "py", "sha256": "cbd4399332943f96", "symbol_count": 17, "symbols": [{"kind": "function", "line": 16, "name": "_make_tech", "signature": "def _make_tech(name, version)"}, {"kind": "class", "line": 21, "name": "TestNginxCveMatch", "signature": "class TestNginxCveMatch"}, {"kind": "class", "line": 31, "name": "TestMetasploitAvailable", "signature": "class TestMetasploitAvailable"}, {"kind": "class", "line": 41, "name": "TestUnknownTechnology", "signature": "class TestUnknownTechnology"}, {"kind": "class", "line": 50, "name": "TestDefaultCredentials", "signature": "class TestDefaultCredentials"}, {"kind": "class", "line": 62, "name": "TestCriticalPrioritised", "signature": "class TestCriticalPrioritised"}, {"kind": "class", "line": 71, "name": "TestNoVersionMatch", "signature": "class TestNoVersionMatch"}, {"kind": "class", "line": 81, "name": "TestAttackReadiness", "signature": "class TestAttackReadiness"}, {"kind": "class", "line": 94, "name": "TestLocalCveLookup", "signature": "class TestLocalCveLookup"}, {"kind": "method", "line": 22, "name": "test_returns_cves_for_nginx", "signature": "def test_returns_cves_for_nginx(self)"}, {"kind": "method", "line": 32, "name": "test_apache_struts_has_metasploit", "signature": "def test_apache_struts_has_metasploit(self)"}, {"kind": "method", "line": 42, "name": "test_unknown_tech_returns_empty", "signature": "def test_unknown_tech_returns_empty(self)"}, {"kind": "method", "line": 51, "name": "test_jenkins_has_default_admin", "signature": "def test_jenkins_has_default_admin(self)"}, {"kind": "method", "line": 63, "name": "test_most_critical_is_highest_cvss", "signature": "def test_most_critical_is_highest_cvss(self)"}, {"kind": "method", "line": 72, "name": "test_no_version_reduces_confidence", "signature": "def test_no_version_reduces_confidence(self)"}, {"kind": "method", "line": 82, "name": "test_exploit_available_increases_score", "signature": "def test_exploit_available_increases_score(self)"}, {"kind": "method", "line": 95, "name": "test_known_tech_in_local_table", "signature": "def test_known_tech_in_local_table(self)"}]}, {"doc": "BDD tests for the web-layer decorators/helpers extracted from `create_app`.  - WH1: `_provides` returns 503 when the service is missing - WH2: `_provides` is transparent when the service is present - WH3: `_provides` runs after `require_auth` (auth ordering preserved) - WH4: `_sse_response` sets the shared streaming headers", "id": "tests/test_web_helpers.py", "kind": "module", "label": "test_web_helpers.py", "language": "py", "sha256": "3d3d040fbabe72a4", "symbol_count": 13, "symbols": [{"kind": "function", "line": 17, "name": "_app", "signature": "def _app()"}, {"kind": "class", "line": 24, "name": "TestWH1Missing", "signature": "class TestWH1Missing"}, {"kind": "class", "line": 39, "name": "TestWH2Present", "signature": "class TestWH2Present"}, {"kind": "class", "line": 54, "name": "TestWH3Ordering", "signature": "class TestWH3Ordering"}, {"kind": "class", "line": 72, "name": "TestWH4Sse", "signature": "class TestWH4Sse"}, {"kind": "method", "line": 25, "name": "test_missing_service_edges_503", "signature": "def test_missing_service_edges_503(self)"}, {"kind": "method", "line": 40, "name": "test_present_service_passes", "signature": "def test_present_service_passes(self)"}, {"kind": "method", "line": 55, "name": "test_guard_runs_after_auth", "signature": "def test_guard_runs_after_auth(self)"}, {"kind": "method", "line": 73, "name": "test_sse_headers", "signature": "def test_sse_headers(self)"}, {"kind": "method", "line": 31, "name": "view", "signature": "def view()"}, {"kind": "method", "line": 46, "name": "view", "signature": "def view()"}, {"kind": "method", "line": 63, "name": "view", "signature": "def view()"}, {"kind": "method", "line": 77, "name": "stream", "signature": "def stream()"}]}, {"doc": "M5 RED tests: tools blueprint slice.", "id": "tests/test_web_tools_blueprint.py", "kind": "module", "label": "test_web_tools_blueprint.py", "language": "py", "sha256": "705d3792a1ccb25b", "symbol_count": 4, "symbols": [{"kind": "function", "line": 5, "name": "test_tools_rules_present", "signature": "def test_tools_rules_present()"}, {"kind": "function", "line": 16, "name": "test_blueprint_registered", "signature": "def test_blueprint_registered()"}, {"kind": "function", "line": 23, "name": "test_unknown_tool_install_reaches_view", "signature": "def test_unknown_tool_install_reaches_view()"}, {"kind": "function", "line": 34, "name": "test_install_route_rejects_bad_binary", "signature": "def test_install_route_rejects_bad_binary()"}]}, {"doc": "Split legacy grouped source files into one addon per file.  Reads every multi-document ``sources/NN_xxx.yaml`` and writes each source as ``sources/NN_xxx/<name>.yaml``, then removes the original grouped file. The runtime loader recurses, so the tree is picked up automatically (lazyaddons style). Re-running is safe: existing per-addon files are overwritten in place.", "id": "tools/split_sources.py", "kind": "module", "label": "split_sources.py", "language": "py", "sha256": "2f0989ec9f02dd26", "symbol_count": 1, "symbols": [{"kind": "function", "line": 19, "name": "main", "signature": "def main()"}]}, {"doc": "tools.sync_docs =============== Generate docs manifest from source truth and fail on drift.  I scan spec/*.md headings plus estorides_core modules plus the live Flask route table, then write docs/MANIFEST.json. CI runs me with --check so stale docs fail the build instead of rotting silently.", "id": "tools/sync_docs.py", "kind": "module", "label": "sync_docs.py", "language": "py", "sha256": "cf47cd90fab5ded9", "symbol_count": 2, "symbols": [{"doc": "Collect specs, core modules and web routes from the tree.", "kind": "function", "line": 20, "name": "collect", "signature": "def collect()"}, {"doc": "Write the manifest or check it for drift.", "kind": "function", "line": 36, "name": "main", "signature": "def main(argv)"}]}, {"doc": "Deprecated entry point. Use:  - `python3 estorides_cli.py serve` for the dev server, or - `gunicorn -w 4 wsgi:app` for production.  This file is kept as a redirect so old launchers that import from this module keep working.", "id": "web.py", "kind": "module", "label": "web.py", "language": "py", "sha256": "0c2c3be7b21bb3b6", "symbol_count": 0, "symbols": []}, {"doc": "estorides.wsgi ==============  WSGI entry point for production deployments.  Run with gunicorn (already pinned in requirements.txt):  gunicorn -w 4 -b 0.0.0.0:5050 --timeout 120 --access-logfile - wsgi:app  Environment variables that matter for prod: ESTORIDES_HOST          bind address (default 127.0.0.1 — loopback only) ESTORIDES_PORT          port (default 5050) ESTORIDES_CORS_ORIGINS  comma-separated allowlist for browser CORS ESTORIDES_HSTS          1 to emit Strict-Transport-Security (only behind TLS) ESTORIDES_FORCE_HTTPS   1 to redirect plain http to https (only behind TLS) ESTORIDES_MAX_BODY_BYTES   int, default 1 MiB  The Werkzeug dev server in estorides_web.py is single-threaded and not safe to expose. Do not use `python3 estorides_web.py` in production; use this entry point.", "id": "wsgi.py", "kind": "module", "label": "wsgi.py", "language": "py", "sha256": "669ffc1f82fbe4e9", "symbol_count": 0, "symbols": []}], "type": "CodePropertyGraph", "version": "1.0"}
```

---

## Architecture Reference

### JS (4 files)

#### `estorides.js`
**Path:** `static/js/estorides.js`
**File Doc:** *Estorides front-end controller*

**Classes:**
- `to` (line 474) - *Panels are keyed by the `<name>-canvas` class, not by id (the map panel's*
- `on` (line 2178) - *Geolocated entities (parsed.lat / parsed.lon) AND country codes. Many parsers stash coords on the entity itself (e.g. abuseipdb has a "countryCode" field). The whole point of v1.1 is to*
- `attribute` (line 3194)

**Functions:**
- `setVisible` (line 12) - *Show/hide that respects the HTML5 `hidden` attribute. `[hidden]` is enforced with `!important` (see estorides_ui.css), so toggling inline `style.display` alone can never reveal an element that carries the attribute — the attribute must be cleared too.*
- `isLanQueryLocal` (line 24)
- `lanRunFields` (line 33)
- `ensureLanConsent` (line 46)
- `detectQueryTypeLocal` (line 128) - *--- UX helpers (v1.4) ----*
- `showToast` (line 139)
- `updateQueryChip` (line 149)
- `setRunProgress` (line 159)
- `showEmptyState` (line 180)
- `summariseObservation` (line 186)
- `buildResultCard` (line 211)
- `requestToolInstall` (line 270)
- `pollToolInstall` (line 293)
- `_installErrMsg` (line 316) - *Turn a failed install result into a meaningful, non-"unknown" message.*
- `populateCategoryFilter` (line 326)
- `applyResultFilters` (line 332)
- `bindResultFilters` (line 353)
- `showFriendlyError` (line 361)
- `focusGraphNodeByValue` (line 374)
- `switchSidebarTab` (line 383)
- `switchCanvasTab` (line 387)
- `clearMap` (line 409)
- `plotPoints` (line 414)
- `replotStreamData` (line 518) - *Rebuild the geospatial + temporal views from everything seen so far. plotPoints clears and redraws from the full coord set, so feeding it the accumulated observations makes the map grow as sources resolve.*
- `stopRunStream` (line 530)
- `renderLanResults` (line 551) - *Dedicated LAN entry point: resolves the scope (query box when it looks like LAN, otherwise the auto-detected private network), asks permission through the blocking modal, then runs a real local inventory scan. The checkbox only widens the ceiling to active; without it the scan still runs passive. Results render below and the graph views refresh from the fed knowledge graph.*
- `startLanScan` (line 620)
- `runQuery` (line 685)
- `runQueryBlocking` (line 773) - *Blocking fallback: the original one-shot render path.*
- `searchEntity` (line 807) - *Deep-search an entity through the full OSINT pipeline without clearing existing data — appends and merges into current state.*
- `handleRunStreamEvent` (line 858)
- `appendStreamObservation` (line 896)
- `appendStreamEntity` (line 918)
- `clearAll` (line 947)
- `setStatus` (line 975)
- `renderResult` (line 981) - *--- result rendering ----*
- `loadAnalysisModels` (line 1029)
- `renderAnalysisModels` (line 1036)
- `makeModelPill` (line 1048)
- `renderAnalysis` (line 1061)
- `renderMarkdownInto` (line 1076) - *Node-only Markdown renderer (CodeQL #38). Remote LLM text must never travel as an HTML string into an innerHTML sink: parse, sanitize and append nodes in one step so there is no string round-trip to flag.*
- `setThinkingVisible` (line 1091)
- `toggleThinking` (line 1098)
- `reanalyze` (line 1103)
- `analyseEntity` (line 1193) - *Analyse a single graph/fusion entity: seed the analysis context with the entity's own data (kept in the Results context so re-analysis uses it) and stream an assessment into the Analysis tab.*
- `expandNode` (line 1226)
- `mergeExpansionIntoGraph` (line 1256) - *Merge a /api/intel/resolve response into the current D3 graph and Leaflet map. Idempotent: re-clicking the same node won't duplicate edges. Returns {nodes, links} counts of what was actually added. Every merge pushes a batch onto window._graphBatches so Ctrl+Z (window.undoGraph) can pop it and repaint without that batch.*
- `drawGraphWithExtras` (line 1320) - *Re-draws the D3 graph with the original nodes/edges PLUS any extras passed in (from a /api/intel/resolve call). The extras are translated to the shape the drawGraph() function already understands (id, label, type, color, size).*
- `pushLink` (line 1344)
- `is3DActive` (line 1367) - *True while the force-graph module owns the canvas in 3D mode.*
- `resolverTypeFor` (line 1395) - *Map a graph node's type/kind onto a resolver/transform entity type.*
- `saveLevelOverrides` (line 1411)
- `levelOf` (line 1415)
- `clusterColor` (line 1419)
- `safeColor` (line 1429) - *Cluster colors come from remote data (TELEMETRY.cluster_palette and per-cluster `color`). Treat them as hostile: only accept a CSS-safe hex or rgb() colour, else fall back to a neutral grey. This keeps a tampered value from ever carrying CSS/HTML into the graph overlays.*
- `deriveClusters` (line 1438) - *Build a clusters[] summary from a flat node list (used after a merge when the server-side clusters array isn't carried along).*
- `hideTooltip` (line 1451) - *--- floating overlays (tooltip + context menu) ----*
- `purifyHTML` (line 1465)
- `setSanitizedHTML` (line 1476) - *Append hostile markup: sanitize with DOMPurify, assign once. When the library is missing, fall back to inert plain text (fail-closed).*
- `showTooltipAt` (line 1485)
- `hideContextMenu` (line 1495)
- `showBridgeTooltip` (line 1500) - *Cross-referenced tooltip for an inter-cluster (bridge) link.*
- `showNodeTooltip` (line 1529)
- `showContextMenu` (line 1548) - *--- context menu: transforms grouped by intel tier ----*
- `setNodeLevel` (line 1623)
- `applyLevelStyles` (line 1632) - *Re-apply level rings to every rendered node circle.*
- `focusNode` (line 1640)
- `runTransform` (line 1654) - *Run a graph pivot transform and merge the result into the graph+map.*
- `runTransformStream` (line 1675) - *Stream a transform over SSE so the graph "explodes" progressively. Shift+click on a context-menu transform uses this path; plain click keeps the single-POST path above. Both merge through the same dedupe (_expansionSeen) and history (_graphBatches) machinery.*
- `undoGraph` (line 1735) - *Undo the last graph expansion (transform / resolve). Rebuilds the dedupe set from the surviving batches and repaints the base graph plus those extras — the popped batch vanishes from the canvas.*
- `selectNode` (line 1770) - *--- side inspector panel ----*
- `add` (line 1781)
- `addText` (line 1787)
- `renderGraphCore` (line 1874) - *--- unified force-graph renderer (clusters + rings + interactions) ----*
- `drawHulls` (line 1962)
- `_redrawGraph` (line 1993) - *Low-level D3 redraw given a flat nodes/links list (back-compat shim).*
- `setStatusDot` (line 1999)
- `showWorkingIndicator` (line 2005)
- `hideWorkingIndicator` (line 2010)
- `toggleTierSection` (line 2015)
- `renderTieredResults` (line 2023)
- `escapeAttr` (line 2089)
- `buildMapCoords` (line 2125)
- `validCoord` (line 2221)
- `colorFor` (line 2225)
- `renderEntities` (line 2244)
- `renderGraphSummary` (line 2296)
- `colorForKind` (line 2325)
- `renderTimeline` (line 2333)
- `fmtTime` (line 2383)
- `filterTimeline` (line 2394)
- `drawGraph` (line 2443) - *--- D3 graph view ----*
- `loadCases` (line 2473)
- `openCaseDetail` (line 2499) - *Rich case modal: loads the saved DB record (query, entities, observations) and offers Analyse (re-run on the case data) and "Load in workspace" (restore entities into the tabs).*
- `restoreCaseToWorkspace` (line 2554) - *Restore a case's saved entities into the workspace tabs (entities list, graph summary, map, timeline) without re-running collection.*
- `buildCaseMapCoords` (line 2575) - *Build map coords from a case's saved entities, reusing the same latitude/longitude resolution and country-centroid fallback as the normal run renderer so a case without explicit lat/lon still drops pins.*
- `renderCaseItem` (line 2593)
- `debounce` (line 2619)
- `escapeHTML` (line 2669) - *--- utils ----*
- `truncate` (line 2674)
- `caseActionSave` (line 2697) - *Bookmark a case. The endpoint prefixes the notes column with "[saved]" so the bookmarked case surfaces in the list at a glance.*
- `caseActionDiff` (line 2718) - *Compare this case to another. The user picks the baseline; the response is rendered inline in a diff panel under the case.*
- `renderCaseDiffPanel` (line 2738) - *Render the diff result below the case. The panel survives until the user reloads the cases list (or opens another diff).*
- `caseActionReport` (line 2779) - *Render the Markdown report. We just dump the text into a modal overlay — keeping it in-browser is enough; the CLI command produces a file copy for sharing.*
- `showReportModal` (line 2815)
- `_sanitizeInput` (line 2840) - *--- generic modal helpers (replace alert/prompt/confirm) ---- User input collected here is treated as hostile: coerced to a string and stripped of control chars before it goes into any request/HTML.*
- `openModal` (line 2843)
- `promptModal` (line 2878) - *Promise-style text prompt. Resolves with a sanitized string or null.*
- `confirmModal` (line 2902)
- `loadSidebarWidth` (line 2994) - *Responsive sidebar toggle + resizable divider.*
- `saveSidebarWidth` (line 3005)
- `loadSidebarCollapsed` (line 3008)
- `saveSidebarCollapsed` (line 3016)
- `switchSidebarTab` (line 3074) - *--- Fusion tab ----*
- `loadFusionTab` (line 3087)
- `loadFusionStats` (line 3093)
- `loadFusionTopChanged` (line 3112)
- `loadFusionSearch` (line 3141)
- `doSearch` (line 3148)
- `loadFusionEntityDetail` (line 3186)
- `_sseUrl` (line 3258)
- `setStatus` (line 3282) - *The discoverer code lives outside the IIFE, so the module-private setStatus is not in scope here. Provide a global one that writes to the*
- `setDiscoverProgress` (line 3289)
- `hideDiscoverProgress` (line 3301)
- `startDiscover` (line 3307)
- `stopDiscover` (line 3390)
- `handleDiscoverEvent` (line 3407)
- `addDiscoverEntityToTab` (line 3446)
- `escapeHtml` (line 3474)
- `maybePlotDiscoverEntity` (line 3479)
- `flushDiscoverEntities` (line 3486)
- `check` (line 3505)
- `t` (line 25)
- `safe` (line 60)
- `done` (line 70)
- `onYes` (line 77)
- `onNo` (line 78)
- `set` (line 105) - *Headers may be a Headers instance, an object, or absent.*
- `TELEMETRY` (line 114)
- `toolBinary` (line 215)
- `status` (line 221)
- `out` (line 319)
- `text` (line 333)
- `cat` (line 334)
- `status` (line 335)
- `scope` (line 621)
- `msg` (line 662)
- `sig` (line 920)
- `boxQ` (line 1122) - *The target being assessed = the current query box (if the user typed something) else the last completed run.*
- `scheduleRender` (line 1146)
- `flush` (line 1151)
- `pump` (line 1172)
- `k` (line 1270)
- `CLUSTER_PALETTE` (line 1388)
- `c` (line 1421)
- `cid` (line 1441)
- `labelFor` (line 1503)
- `c` (line 1504)
- `tr` (line 1598)
- `barH` (line 1644)
- `flush` (line 1681)
- `k` (line 1748)
- `tr` (line 1838)
- `barH` (line 1893)
- `obs` (line 2337)
- `frac` (line 2370)
- `q` (line 2474)
- `entities` (line 2500)
- `obs` (line 2501)
- `saved` (line 2597) - *Saved cases get a visible bookmark pill so the operator can scan the list for "things I came back to" at a glance.*
- `rows` (line 2746)
- `removed` (line 2749)
- `actions` (line 2849)
- `close` (line 2863)
- `tag` (line 2964)
- `_sseAuthToken` (line 3254) - *Auth token for SSE (EventSource can't set custom headers).*
- `sig` (line 3451) - *Avoid duplicates with the simple in-memory check.*

#### `graph_bundle.js`
**Path:** `static/js/graph_bundle.js`
**File Doc:** *Estorides Bundles — circle 2D + sphere 3D hierarchical edge bundling.*

**Functions:**
- `djb2KindColor` (line 32)
- `mk` (line 60)
- `clip` (line 66)
- `tabActive` (line 70)
- `ink` (line 74)
- `colorOf` (line 88)
- `keyOf` (line 94)
- `bspline` (line 99)
- `curve` (line 120)
- `buildCurves` (line 137)
- `resize` (line 148)
- `radius` (line 160)
- `center` (line 164)
- `makeProjector` (line 165)
- `depthAlpha` (line 175)
- `focusState` (line 177)
- `edgeState` (line 185)
- `nodeLit` (line 205)
- `strokeCurve` (line 216)
- `pointOn` (line 226)
- `g` (line 229)
- `draw` (line 231)
- `drawGroups` (line 291)
- `nodeRadius` (line 333)
- `screenNodes` (line 337)
- `drawNodes` (line 343)
- `labelSet` (line 355)
- `drawLabels` (line 367)
- `drawHud` (line 405)
- `add` (line 411)
- `hitNode` (line 420)
- `hitGroup` (line 443)
- `tipFor` (line 461)
- `flowsOf` (line 483)
- `rank` (line 492)
- `inspectInGraph` (line 497)
- `nodeButton` (line 504)
- `groupButton` (line 512)
- `listInto` (line 519)
- `renderPanel` (line 529)
- `renderLegend` (line 613)
- `focusNode` (line 645)
- `focusGroup` (line 650)
- `facing` (line 655)
- `aimAt` (line 661)
- `setView` (line 667)
- `setColor` (line 671)
- `setDir` (line 675)
- `setBeta` (line 679)
- `sync` (line 687)
- `writeHash` (line 697)
- `readHash` (line 709)
- `ingest` (line 726)
- `refresh` (line 762)
- `frame` (line 786)
- `init` (line 812)
- `search` (line 913)
- `t0` (line 275)
- `tt` (line 279)
- `mid` (line 306)
- `tag` (line 958)
- `v` (line 78)

#### `graph_force.js`
**Path:** `static/js/graph_force.js`
**File Doc:** *Estorides force-graph module (spec/graph_force3d.md).*

**Functions:**
- `reducedMotion` (line 52)
- `toast` (line 57)
- `fail` (line 65)
- `settings` (line 72)
- `adaptLocal` (line 79) - *--- adapt /api/graph nodes to RAW force-graph shape (fallback when the server did not send data.force; mirrors graph_force.py) ----*
- `currentRaw` (line 137)
- `rebuildRaw` (line 153)
- `indexRaw` (line 179)
- `lkey` (line 218)
- `colorOf` (line 219)
- `dimmed` (line 220)
- `hiddenKind` (line 221)
- `visiblePayload` (line 226)
- `computeHighlight` (line 275)
- `tip` (line 298)
- `refreshFamList` (line 315)
- `famAnchor` (line 320)
- `famOf` (line 330)
- `clusterForce` (line 335)
- `force` (line 337)
- `ringOf` (line 352)
- `radialForce` (line 360)
- `force` (line 362)
- `collideForce` (line 377)
- `force` (line 379)
- `radius` (line 402)
- `linkStrengthFn` (line 406)
- `setEngineButtons` (line 422) - *var base = st.linkStrength; var src = l.source.id || l.source, tgt = l.target.id || l.target; if (l.type === 'layered_as') return S.layout === 'radial' ? 0 : base * 0.15; if (l.type === 'member_of') return S.layout === 'radial' ? base * 0.05 : base * 0.6; if (S.layout === 'cluster') { var a = byId[src], b = byId[tgt]; return a && b && famOf(a) === famOf(b) ? base : base * 0.08; } if (S.layout === 'radial') return base * 0.35; return base; }; } /* ---- 3D mount ----*
- `show3DChrome` (line 435) - *Exclusive render: exactly one engine owns the pixels. Entering 3D hides every D3 SVG layer; leaving 3D hides the stage and pauses the 3D loop so a hidden WebGL canvas can never stack over 2D or steal pointer events.*
- `mount3D` (line 458)
- `startOverlayLoop` (line 546)
- `overlayTick` (line 551)
- `convexHullPts` (line 561)
- `cross` (line 564)
- `inflateHull` (line 580)
- `traceSmooth` (line 592)
- `rr` (line 610)
- `glyphPath` (line 619)
- `tierColorOf` (line 647)
- `drawGlyph3D` (line 651)
- `drawLabel3D` (line 703)
- `paintOverlay3D` (line 724)
- `stageLocal` (line 839)
- `hitNode3D` (line 845)
- `segDist` (line 854)
- `hitEdge3D` (line 861)
- `hoverAt3D` (line 876)
- `bindStagePointer` (line 887)
- `refresh3D` (line 939)
- `reload3D` (line 945)
- `resize3D` (line 950)
- `applyLayout3D` (line 961)
- `onSelect3D` (line 982) - *Click selects and inspects only. Double click, the Enter key or the Expand button pivots through the resolver. Single click never mutates the graph, so inspecting in 3D can no longer repaint a D3 layer on top of the scene.*
- `expandSelected` (line 999)
- `focusSelected` (line 1009)
- `copyDeepLink` (line 1020)
- `done` (line 1022)
- `reheat` (line 1030)
- `toggleBridges` (line 1038)
- `snapshot3D` (line 1048) - *PNG export composites the WebGL link field with the glyph overlay, so the snapshot matches the screen (native node spheres stay hidden).*
- `toggleOrbit` (line 1074)
- `onEdge3D` (line 1088) - *Edge inspection in 3D: bridge edges open the cross-reference tooltip, plain edges report their relation. Hover only changes the cursor.*
- `clearSelection` (line 1112)
- `focusFamily` (line 1125)
- `markFamilies` (line 1150)
- `hud` (line 1156)
- `stat` (line 1160)
- `renderLegend` (line 1187) - *(S.bridgesOnly ? ' · bridges' : ''); box.appendChild(m); if (S.hl.length) box.appendChild(stat(S.hl.length, 'highlighted')); if (S.selected && byId[S.selected]) { var s = document.createElement('span'); s.appendChild(document.createTextNode('focus ')); var b = document.createElement('b'); b.textContent = short(byId[S.selected].label, 24); s.appendChild(b); box.appendChild(s); } } /* ---- legend: kind pills + community families ----*
- `applyFilters` (line 1241)
- `runSearch` (line 1276)
- `pickHit` (line 1342)
- `to3D` (line 1353) - *} box.classList.add('open'); } function pickHit(i) { var x = hits[i]; if (!x) return; var box = $('gf-results'); if (box) box.classList.remove('open'); if (S.engine === '3d') onSelect3D(x.n, null); else if (x.n._src) B.selectNode(x.n._src); else B.selectNode(x.n); } /* ---- engine toggle ----*
- `to2D` (line 1362)
- `readHash` (line 1371)
- `writeHash` (line 1386)
- `syncIsolateBtn` (line 1393)
- `wireToolbar` (line 1398)
- `toggle` (line 1423)
- `ordered` (line 96)
- `cid` (line 104)
- `fam` (line 105)
- `src` (line 162)
- `s` (line 189)
- `t` (line 190)
- `entities` (line 205)
- `links` (line 233)
- `s` (line 234)
- `t` (line 235)
- `s` (line 246)
- `t` (line 247)
- `s` (line 253)
- `t` (line 254)
- `s` (line 259)
- `t` (line 260)
- `a` (line 327)
- `m` (line 392)
- `col` (line 788)
- `topN` (line 816)
- `s` (line 1138)
- `t` (line 1139)
- `nodes` (line 1249)
- `edges` (line 1256)
- `s` (line 1257)
- `t` (line 1258)
- `fn` (line 1267)
- `tag` (line 1511)
- `short` (line 21)
- `esc` (line 25)
- `mid` (line 600)

#### `source_manager.js`
**Path:** `static/js/source_manager.js`
**File Doc:** *Estorides Source Manager — form-based YAML editor*

**Classes:**
- `from` (line 255)

**Functions:**
- `authHeaders` (line 7) - */* Estorides Source Manager — form-based YAML editor (function () { 'use strict'; /* ─── auth ───*
- `apiFetch` (line 15)
- `getCheckedTags` (line 67) - *contact: $('field-contact'), logsQueries: $('field-logs-queries'), toolUrl: $('field-tool-url'), toolMethod: $('field-tool-method'), toolHeaders: $('field-tool-headers'), toolParams: $('field-tool-params'), toolBody: $('field-tool-body'), pagStrategy: $('field-pag-strategy'), pagLimit: $('field-pag-limit'), pagParam: $('field-pag-param'), pagCursorPath: $('field-pag-cursor-path'), }; /* ─── get checked tags ───*
- `setCheckedTags` (line 74)
- `readForm` (line 84) - *var checks = container.querySelectorAll('input[type="checkbox"]:checked'); return Array.from(checks).map(function (c) { return c.value; }); } function setCheckedTags(containerId, values) { var container = $(containerId); if (!container) return; var vals = new Set(values || []); container.querySelectorAll('input[type="checkbox"]').forEach(function (c) { c.checked = vals.has(c.value); }); } /* ─── read form → source object ───*
- `writeForm` (line 120) - *try { var b = JSON.parse(fields.toolBody.value.trim() || '{}'); if (Object.keys(b).length) s.tool.body = b; } catch (e) {} Pagination var strat = fields.pagStrategy.value; if (strat) { s.pagination = { strategy: strat }; var lim = parseInt(fields.pagLimit.value, 10); if (lim > 0) s.pagination.limit = lim; if (fields.pagParam.value.trim()) s.pagination.param = fields.pagParam.value.trim(); if (fields.pagCursorPath.value.trim()) s.pagination.cursor_path = fields.pagCursorPath.value.trim(); } return s; } /* ─── write source object → form ───*
- `updateYamlPreview` (line 183) - *fields.pagCursorPath.value = pag.cursor_path || ''; setCheckedTags('field-applies-to', s.applies_to); setCheckedTags('field-entity-hints', s.entity_hints); currentName = s.name; isDirty = false; formStatus.textContent = ''; formStatus.className = 'form-status'; deleteBtn.hidden = false; updateYamlPreview(); } /* ─── update YAML preview ───*
- `renderList` (line 193) - *updateYamlPreview(); } /* ─── update YAML preview ─── function updateYamlPreview() { try { var s = readForm(); yamlPreview.textContent = JSON.stringify(s, null, 2); } catch (e) { yamlPreview.textContent = '/* cannot render preview */'; } } /* ─── render source list ───*
- `escHtml` (line 223) - *var keyBadge = s.requires_key ? '<span class="src-item-key-badge">key</span>' : ''; var sysBadge = s.kind === 'system_app' ? '<span class="src-item-sys-badge">sys</span>' : ''; html += '<div class="src-item' + active + '" data-name="' + escAttr(s.name) + '">' + '<span class="src-item-icon ' + onOff + '"></span>' + '<div class="src-item-info">' + '<div class="src-item-name">' + escHtml(s.name) + sysBadge + keyBadge + '</div>' + '<div class="src-item-cat">' + escHtml(s.description || '') + '</div>' + '</div></div>'; }); }); listEl.innerHTML = html || '<div style="padding:20px;text-align:center;color:var(--text-2);font-size:13px;">No sources match filter</div>'; } /* ─── helpers ───*
- `escAttr` (line 224)
- `toast` (line 227) - *'<div class="src-item-info">' + '<div class="src-item-name">' + escHtml(s.name) + sysBadge + keyBadge + '</div>' + '<div class="src-item-cat">' + escHtml(s.description || '') + '</div>' + '</div></div>'; }); }); listEl.innerHTML = html || '<div style="padding:20px;text-align:center;color:var(--text-2);font-size:13px;">No sources match filter</div>'; } /* ─── helpers ─── function escHtml(s) { return String(s).replace(/[&<>"]/g, function (m) { return ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' })[m]; }); } function escAttr(s) { return String(s).replace(/"/g, '&quot;'); } /* ─── toast ───*
- `loadSources` (line 236) - */* ─── helpers ─── function escHtml(s) { return String(s).replace(/[&<>"]/g, function (m) { return ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' })[m]; }); } function escAttr(s) { return String(s).replace(/"/g, '&quot;'); } /* ─── toast ─── function toast(msg, kind) { var el = document.createElement('div'); el.className = 'toast ' + (kind || 'info'); el.textContent = msg; document.getElementById('toast-stack').appendChild(el); setTimeout(function () { if (el.parentNode) el.parentNode.removeChild(el); }, 4000); } /* ─── load sources ───*
- `clearEditor` (line 251) - *apiFetch('/api/sources/yaml').then(function (data) { sources = data.sources || []; srcCount.textContent = data.total + ' sources'; renderList(filterInput.value); If currently selected source still exists, keep it; otherwise clear if (currentName && !data.sources.some(function (s) { return s.name === currentName; })) { clearEditor(); } }).catch(function (err) { toast('Failed to load sources: ' + err.message, 'err'); }); } /* ─── clear editor ───*
- `selectSource` (line 261) - *}); } /* ─── clear editor ─── function clearEditor() { form.hidden = true; editorEmpty.hidden = false; writeForm(null); Remove active class from all list items listEl.querySelectorAll('.src-item.active').forEach(function (el) { el.classList.remove('active'); }); currentName = null; } /* ─── select source ───*
- `saveSource` (line 273) - */* ─── select source ─── function selectSource(name) { var s = sources.filter(function (s) { return s.name === name; })[0]; if (!s) return; form.hidden = false; editorEmpty.hidden = true; writeForm(s); listEl.querySelectorAll('.src-item.active').forEach(function (el) { el.classList.remove('active'); }); var item = listEl.querySelector('.src-item[data-name="' + escAttr(name) + '"]'); if (item) item.classList.add('active'); } /* ─── save source ───*
- `deleteSource` (line 304) - *toast('Source "' + data.name + '" saved', 'ok'); formStatus.textContent = 'Saved'; formStatus.className = 'form-status ok'; loadSources(); Re-select the saved source selectSource(data.name); }).catch(function (err) { toast('Save failed: ' + err.message, 'err'); formStatus.textContent = 'Error: ' + err.message; formStatus.className = 'form-status err'; }); } /* ─── delete source ───*
- `newSource` (line 334) - *overlay.remove(); apiFetch('/api/sources/yaml/' + encodeURIComponent(currentName), { method: 'DELETE' }).then(function () { toast('Source "' + currentName + '" deleted', 'ok'); clearEditor(); loadSources(); }).catch(function (err) { toast('Delete failed: ' + err.message, 'err'); }); }); document.getElementById('confirm-cancel').addEventListener('click', function () { overlay.remove(); }); overlay.addEventListener('click', function (e) { if (e.target === overlay) overlay.remove(); }); } /* ─── new source ───*

### PY (185 files)

#### `gb_shots.py`
**Path:** `.scratchpad/gb_shots.py`

*No symbols extracted*

#### `gfv2_bridge.py`
**Path:** `.scratchpad/gfv2_bridge.py`

*No symbols extracted*

#### `gfv2_click.py`
**Path:** `.scratchpad/gfv2_click.py`

*No symbols extracted*

#### `gfv2_data.py`
**Path:** `.scratchpad/gfv2_data.py`

*No symbols extracted*

#### `gfv2_debug.py`
**Path:** `.scratchpad/gfv2_debug.py`

*No symbols extracted*

#### `gfv2_dom.py`
**Path:** `.scratchpad/gfv2_dom.py`

*No symbols extracted*

#### `gfv2_func.py`
**Path:** `.scratchpad/gfv2_func.py`

*No symbols extracted*

#### `gfv2_raw.py`
**Path:** `.scratchpad/gfv2_raw.py`

*No symbols extracted*

#### `gfv2_shots.py`
**Path:** `.scratchpad/gfv2_shots.py`

*No symbols extracted*

#### `gfv2_step.py`
**Path:** `.scratchpad/gfv2_step.py`

*No symbols extracted*

#### `gfv2_time.py`
**Path:** `.scratchpad/gfv2_time.py`

*No symbols extracted*

#### `app.py`
**Path:** `app.py`
**File Doc:** *Deprecated entry point. Use:  - the `estorides` console script (installed by `pip install -e .`), or - `python3 estorides_cli.py serve` for the dev server, or - `gunicorn -w 4 wsgi:app` for production.  This file is kept as a redirect so old launchers that import `app` from this module keep working. It intentionally does NOT start a server on import.*

*No symbols extracted*

#### `estorides_cli.py`
**Path:** `estorides_cli.py`
**File Doc:** *estorides CLI.  Usage: estorides "example.com" estorides "8.8.8.8" --include-paid estorides "user@example.com" --only-sources shodan_internetdb,ipapi_free estorides graph --export graphml estorides report --query "example.com" --out report.md estorides stix --out bundle.json estorides serve --port 5050*

**Functions:**
- `_setup_logging` (line 39) `def _setup_logging(verbose)`
- `_collect_selectors` (line 46) `def _collect_selectors(events, types)` - *Group discovered entity values by type for the requested type set.

Used to surface the human selectors (emails, usernames, persons, orgs,
phones) a discover run found, separately from the infrastructure list.*
- `_resolve_proxy` (line 62) `def _resolve_proxy(args)` - *Resolve the egress proxy from the OPSEC flags.

`--tor` is a convenience alias for the default local Tor SOCKS port;
an explicit `--proxy` wins over it. Returns None when neither is set,
in which case the engine still honours the env-configured proxy pool.*
- `_add_opsec_flags` (line 75) `def _add_opsec_flags(parser)` - *Attach the shared operator-OPSEC flags to a subcommand parser.*
- `cmd_discover` (line 93) `def cmd_discover(args)` - *v1.2 — fanout the surface from a seed.

Mirrors the /api/discover/start endpoint but as a CLI subcommand
so an operator can drop a seed in a terminal and walk away.
Streams progress to stdout. The final case is dumped to
--out-json if provided.*
- `cmd_run` (line 205) `def cmd_run(args)`
- `cmd_scope` (line 282) `def cmd_scope(args)` - *Classify discovered assets against a program's scope rules.

Reads assets (a `discover --out-json` surface or a flat host list),
applies the in/out-of-scope rules, and emits the in-scope host/IP
lists an operator pipes into the active phase. Out-of-scope assets are
surfaced explicitly so they are never targeted by accident.*
- `cmd_graph_export` (line 328) `def cmd_graph_export(args)`
- `cmd_export_stix` (line 357) `def cmd_export_stix(args)`
- `cmd_export_misp` (line 367) `def cmd_export_misp(args)`
- `cmd_report` (line 377) `def cmd_report(args)` - *Render a Markdown report for a case.

Reads from the case store (so the report reflects what was persisted,
not the volatile in-memory graph). When `--diff <other_id>` is given,
the report also includes a "what's new" section vs the other case.*
- `cmd_diff` (line 418) `def cmd_diff(args)` - *Diff two cases. CLI twin of /api/cases/diff.*
- `cmd_status` (line 446) `def cmd_status(_)`
- `cmd_fusion` (line 453) `def cmd_fusion(args)` - *Query the cross-run fusion datastore.

Subactions:
  stats              size of the fused fact base
  sources            YAML catalogue with fetch/ok counters
  entities [TERM]    search fused entities (``--type``, ``--min-sources``)
  entity ID          full fused view of one entity (provenance + props)*
- `cmd_watch_add` (line 496) `def cmd_watch_add(args)` - *Add a new recurring watch target.*
- `_watch_runner_factory` (line 538) `def _watch_runner_factory(proxy, passive_only)` - *Create an async watch runner wired to the Orchestrator.

Returns an async function that takes a WatchTarget and returns
the orchestrator's run result dict.*
- `cmd_watch_list` (line 560) `def cmd_watch_list(args)` - *List all watch targets.*
- `cmd_watch_remove` (line 580) `def cmd_watch_remove(args)` - *Delete a watch target.*
- `cmd_watch_enable` (line 592) `def cmd_watch_enable(args)`
- `cmd_watch_disable` (line 605) `def cmd_watch_disable(args)`
- `cmd_watch_history` (line 617) `def cmd_watch_history(args)`
- `cmd_alerts_test` (line 638) `def cmd_alerts_test(args)`
- `cmd_alerts_channels` (line 650) `def cmd_alerts_channels(args)`
- `cmd_scheduler_start` (line 662) `def cmd_scheduler_start(args)`
- `cmd_scheduler_stop` (line 672) `def cmd_scheduler_stop(args)`
- `cmd_scheduler_status` (line 682) `def cmd_scheduler_status(args)`
- `cmd_serve` (line 690) `def cmd_serve(args)`
- `build_parser` (line 707) `def build_parser()`
- `main` (line 863) `def main(argv)`
- `_on_done` (line 222) `def _on_done(source_name, ok, status, elapsed_ms)`
- `_run` (line 546) `def _run(watch)`

#### `__init__.py`
**Path:** `estorides_core/__init__.py`
**File Doc:** *estorides_core.__init__*

*No symbols extracted*

#### `active_recon.py`
**Path:** `estorides_core/active_recon.py`

**Classes:**
- `NmapResult` (line 13) `class NmapResult`
- `NiktoResult` (line 32) `class NiktoResult`
- `SqlmapResult` (line 49) `class SqlmapResult`
- `DnsreconResult` (line 66) `class DnsreconResult`
- `TheHarvesterResult` (line 85) `class TheHarvesterResult`

**Methods:**
- `_parse_nmap_stdout` (line 102) `def _parse_nmap_stdout(stdout, tool_name)`
- `_parse_nikto_stdout` (line 125) `def _parse_nikto_stdout(stdout, tool_name)`
- `_parse_sqlmap_stdout` (line 144) `def _parse_sqlmap_stdout(stdout, tool_name)`
- `_parse_dnsrecon_stdout` (line 171) `def _parse_dnsrecon_stdout(stdout, tool_name)`
- `_parse_harvester_stdout` (line 188) `def _parse_harvester_stdout(stdout, tool_name)`
- `run_nmap` (line 213) `def run_nmap(target, args)`
- `run_nikto` (line 243) `def run_nikto(target, args)`
- `run_sqlmap` (line 269) `def run_sqlmap(target, args)`
- `run_dnsrecon` (line 295) `def run_dnsrecon(target, args)`
- `run_theHarvester` (line 325) `def run_theHarvester(target, args)`
- `to_dict` (line 24) `def to_dict(self)`
- `to_entities` (line 27) `def to_entities(self)`
- `to_dict` (line 41) `def to_dict(self)`
- `to_entities` (line 44) `def to_entities(self)`
- `to_dict` (line 58) `def to_dict(self)`
- `to_entities` (line 61) `def to_entities(self)`
- `to_dict` (line 77) `def to_dict(self)`
- `to_entities` (line 80) `def to_entities(self)`
- `to_dict` (line 95) `def to_dict(self)`
- `to_entities` (line 98) `def to_entities(self)`

#### `alerter.py`
**Path:** `estorides_core/alerter.py`
**File Doc:** *estorides_core.alerter ====================== Alert channel dispatcher for watch target notifications.  Supports: * Slack webhook * Discord webhook (embedded messages) * Telegram bot * Email (SMTP) * Generic webhook (HTTP POST)  Each channel reads its credentials from environment variables so they are never stored in the database.*

**Classes:**
- `_NoRedirectHandler` (line 53) `class _NoRedirectHandler(HTTPRedirectHandler)` - *Refuse every HTTP redirect.

``urlopen`` follows 301/302/307/308 by default, which would let a
malicious webhook pivot to an SSRF-blocked target (e.g. cloud metadata
at 169.254.169.254) *after* passing the pre-flight ``check_url`` guard.
Returning ``None`` makes the opener raise ``HTTPError`` instead of
issuing a second request. Alert delivery fails closed; legitimate
webhook endpoints (Slack/Discord/Telegram) do not redirect POSTs.*
- `AlertDispatcher` (line 194) `class AlertDispatcher` - *Central alert dispatcher: routes alerts to configured channels.

Reads channel credentials from environment variables on each dispatch
so an operator can hot-configure without restarting.*

**Functions:**
- `_check_cooldown` (line 42) `def _check_cooldown(channel)`

**Methods:**
- `_http_post` (line 77) `def _http_post(url, payload)` - *POST JSON payload to URL, return True on success.

The destination is treated as hostile: every webhook URL comes from
operator-owned environment variables (never from a caller-supplied
string) and is validated by the central SSRF guard before any socket
is opened. Raw caller-supplied URLs are refused outright (CodeQL #47):
a ``channel`` starting with ``http`` can never become a request
destination. Redirects are never followed (see ``_NoRedirectHandler``):
the guard vetted the first hop only.*
- `_send_slack` (line 111) `def _send_slack(webhook_url, title, body, severity)` - *Send a Slack message via Incoming Webhook.*
- `_send_discord` (line 126) `def _send_discord(webhook_url, title, body, severity)` - *Send a Discord embed via Webhook.*
- `_send_telegram` (line 141) `def _send_telegram(bot_token, chat_id, title, body, severity)` - *Send a Telegram message via Bot API.*
- `_send_email` (line 156) `def _send_email(smtp_host, smtp_port, smtp_user, smtp_pass, from_addr, to_addr, title, body, severity)` - *Send an email alert via SMTP.*
- `_send_webhook` (line 177) `def _send_webhook(webhook_url, title, body, severity)` - *Send a generic webhook POST.*
- `_fmt_time` (line 314) `def _fmt_time(ts)`
- `redirect_request` (line 64) `def redirect_request(self, req, fp, code, msg, headers, newurl)`
- `send` (line 201) `def send(self, channel, title, body, severity)` - *Send an alert to a single channel. Returns True on success.*
- `send_watch_alert` (line 260) `def send_watch_alert(self, watch, entity_count, obs_count, new_entities)` - *Send alerts for a completed watch run to all configured channels.*
- `test` (line 280) `def test(self, channel)` - *Send a test alert to verify channel configuration.*
- `available_channels` (line 290) `def available_channels(self)` - *Return list of configured channels with their status.*

#### `async_client.py`
**Path:** `estorides_core/async_client.py`
**File Doc:** *estorides_core.async_client =========================== Async HTTP client with: * per-host concurrency limits * exponential backoff * circuit breaker (per host) * disk-backed cache (SQLite) * automatic key-substitution from environment  Designed so a single query can fan out to 50+ sources in parallel without DOSsing any of them.*

**Classes:**
- `CircuitBreaker` (line 64) `class CircuitBreaker` - *Per-host circuit breaker.*
- `ResponseCache` (line 86) `class ResponseCache` - *SQLite-backed response cache. Key = (url + method + body hash).

Entries carry their write timestamp and are only served while younger
than `ttl_seconds`; a stale row is ignored (and lazily overwritten on
the next live fetch) so the cache can never pin down OSINT that has
since changed or been taken down.*
- `AsyncClient` (line 172) `class AsyncClient` - *Async HTTP client with retries, backoff, circuit breaker, cache.*

**Functions:**
- `_is_socks` (line 49) `def _is_socks(proxy)` - *True when the proxy URL is a SOCKS proxy (e.g. Tor).*
- `_redact_proxy` (line 54) `def _redact_proxy(proxy)` - *Strip any `user:pass@` credentials from a proxy URL before logging.*

**Methods:**
- `sync_fetch` (line 400) `def sync_fetch(method, url)`
- `allow` (line 69) `def allow(self, host)`
- `record_success` (line 75) `def record_success(self, host)`
- `record_failure` (line 79) `def record_failure(self, host)`
- `__init__` (line 95) `def __init__(self, path)`
- `_conn` (line 109) `def _conn(self)` - *Short-lived connection that is always closed.

`with sqlite3.connect(...)` only commits/rolls back — it does NOT
close the connection, so the old code leaked one connection per
cache read/write until GC.*
- `_init_db` (line 123) `def _init_db(self)`
- `_key` (line 136) `def _key(method, url, body)`
- `get` (line 145) `def get(self, method, url, body)`
- `set` (line 161) `def set(self, method, url, body, value)`
- `__init__` (line 175) `def __init__(self)`
- `__aenter__` (line 206) `def __aenter__(self)`
- `_next_http_proxy` (line 250) `def _next_http_proxy(self)` - *Round-robin the next HTTP proxy, or None (SOCKS/connector or direct).*
- `__aexit__` (line 258) `def __aexit__(self)`
- `session` (line 264) `def session(self)`
- `fetch` (line 270) `def fetch(self, method, url)` - *Fetch a URL. Returns (parsed_data, meta).

meta contains status, content_type, cached, attempts, error.
parsed_data is dict/list/str/None depending on content-type.*

#### `audit.py`
**Path:** `estorides_core/audit.py`
**File Doc:** *estorides_core.audit ==================== Append-only audit log + per-IP rate limiter for the web layer.  Two responsibilities:  AuditLog Every API call (query, run, export) gets a JSON line appended to `sessions/audit.jsonl` (one line per request) with: timestamp, remote IP, query, sources used, observation count, status, runtime ms, result size. This is the compliance trail that "state level" platforms must produce on demand.  The log is append-only and best-effort: a disk error never breaks the request, it just records the failure and moves on. Concurrent writers are safe via a process-level lock (we only have one web process in the default deployment; for multi-worker setups we'd switch to a queue, but the file size stays small enough that the current model is fine).  RateLimiter Simple sliding-window rate limiter, in-memory. Default: 30 requests per minute per IP. Configurable via env. The window is sliding rather than fixed-bucket so a burst of 30 right at the boundary doesn't get a free second batch.  This module is dependency-free. It does NOT replace a real WAF or production-grade rate limiter (Redis-backed, distributed). It is a defensible default for self-hosted deployments and a starting point*

**Classes:**
- `AuditEvent` (line 55) `class AuditEvent`
- `AuditLog` (line 72) `class AuditLog` - *Append-only JSONL audit log with a size cap.

Thread-safe via a process-level lock. The on-disk file is opened
per-write so a long-running process never holds an exclusive handle
(which would defeat any out-of-band log rotation tooling).

Issue #48: the log has no built-in rotation, so a busy deployment
grows the file without bound. We apply a soft size cap
(`ESTORIDES_AUDIT_MAX_BYTES`, default 50 MiB). When the cap is
hit we rotate in place: rename the current file to
`audit.jsonl.1` and start fresh. Older rotated copies are
removed so the directory doesn't grow without bound either —
only the active file and the previous rotation survive. The
cap is checked before each write so the cost is one stat per
record.*
- `RateLimiter` (line 180) `class RateLimiter` - *In-process sliding-window rate limiter.

The bucket state is a `dict[str, deque[float]]` held under a
`threading.Lock`. **The state is per-process** — when the
application is deployed with gunicorn's default multi-worker mode
(`-w 4`, as documented in `wsgi.py`), each worker process has its
own counter and the effective rate limit is `N_workers * limit`.

For a multi-worker deployment there are two safe options:

1.  Set `ESTORIDES_RATE_LIMIT = ceil(desired_total / N_workers)`,
    so the per-worker cap × worker count approximates the
    documented limit. This is the recommended shape for a
    single-tenant deployment behind a reverse proxy that does
    not share state.
2.  Swap this class for a Redis-backed implementation; the call
    sites only depend on `allow()` returning a bool, so the swap
    is local to this module. Issue #38 documents the trade-off.*

**Methods:**
- `to_jsonl` (line 68) `def to_jsonl(self)`
- `__init__` (line 90) `def __init__(self, path)`
- `record` (line 104) `def record(self, event)`
- `_maybe_rotate_locked` (line 115) `def _maybe_rotate_locked(self)` - *If the active file is over the cap, rotate in place.

Caller must hold `self._lock`. We use a per-write stat-then-
write sequence; the race window (another process also writing)
is acceptable for a single-tenant audit log — the worst case
is a slightly oversized file, not data loss.*
- `query` (line 150) `def query(self, event)`
- `__init__` (line 201) `def __init__(self)`
- `allow` (line 214) `def allow(self, key)` - *Return (allowed, retry_after_seconds).

`retry_after_seconds` is 0 when allowed, otherwise the number of
seconds the caller should wait before retrying.

The configured `max_requests` is re-read from the environment on
every call so an operator can hot-tune the limit without a
process restart. (The window itself is stable for a process
lifetime, which is fine for our deployment shape.)*
- `reset` (line 238) `def reset(self, key)`

#### `case_crypto.py`
**Path:** `estorides_core/case_crypto.py`
**File Doc:** *estorides_core.case_crypto ========================== Opt in field encryption for case notes and analysis at rest.  I read a Fernet key from ESTORIDES_CASE_KEY only. Absent, malformed or missing backend means disabled and plaintext. I never raise on crypto failure and I never log the key.*

**Functions:**
- `_load_fernet` (line 19) `def _load_fernet()` - *Return a Fernet instance when configured and available else None.*
- `crypto_status` (line 36) `def crypto_status()` - *Report whether field encryption is active and why.*
- `encrypt_text` (line 50) `def encrypt_text(plain)` - *Encrypt plain text when enabled else return it unchanged.*
- `decrypt_text` (line 65) `def decrypt_text(token)` - *Decrypt a token, passing through plaintext and errors safely.*

#### `cases.py`
**Path:** `estorides_core/cases.py`
**File Doc:** *estorides_core.cases ==================== Run persistence — every intelligence query becomes a "case" that survives across sessions.  Why a separate module: The orchestrator already writes a JSONL dataset line per run, but that format is append-only and has no indexes. We need cheap lookups by query, by entity, by date range, and a way to reconstruct a previous case (observations + entities + graph + analysis) without grepping a file.  Schema:  cases         (id, query, query_type, created_at, status, notes, source_count, obs_count, entity_count, mitre_json, analysis_json, kg_path) observations  (id, case_id, source, category, parser, parsed_json, raw_text, meta_json, ontology_json, mitre_json) case_entities (case_id, type, value, source, confidence, sources_json)  `cases` is the parent; observations and case_entities have FK to it via ON DELETE CASCADE so dropping a case cleans up.  Public surface:  store = CaseStore() case_id = store.create_case(query, query_type) store.add_observation(case_id, observation)*

**Classes:**
- `CaseStore` (line 112) `class CaseStore(SqliteStore)` - *Thread-safe SQLite-backed case repository.

SQLite is plenty for OSINT-sized workloads (a few thousand cases
per operator per month) and avoids a separate service. The
underlying file is shared with the cache if you point both env
vars at the same path; otherwise we live in `estorides_cases.sqlite`
next to it.*

**Methods:**
- `_init_schema` (line 136) `def _init_schema(self)` - *Create base tables then best effort FTS index.*
- `fts_available` (line 147) `def fts_available(self)` - *True when the FTS5 index exists and answers queries.*
- `search_observations_fts` (line 151) `def search_observations_fts(self, query, limit)` - *Full text search over observations ordered by rank.*
- `create_case` (line 173) `def create_case(self, query, query_type, notes)` - *Open a new case and return its id (8-char slug).*
- `add_observation` (line 189) `def add_observation(self, case_id, observation)` - *Persist a single observation row.

The full parsed/raw payload is JSON-encoded so we can
reconstruct the run later without re-running the source.*
- `add_entities` (line 223) `def add_entities(self, case_id, entities)` - *Persist the merged entity list. Duplicate (type, value) rows
for the same case are silently ignored — the PK is the guard.*
- `finalise` (line 246) `def finalise(self, case_id, analysis, kg_path, mitre, source_count, obs_count, entity_count, status)`
- `delete_case` (line 276) `def delete_case(self, case_id)`
- `set_notes` (line 280) `def set_notes(self, case_id, notes)` - *Overwrite the free-text `notes` column for a case.

Used by the UI "save/bookmark" gesture. Kept here (rather than
reaching into `_lock`/`_conn` from the route) so the store owns
its own transaction boundary.*
- `get_case` (line 291) `def get_case(self, case_id)`
- `list_observations` (line 304) `def list_observations(self, case_id)`
- `list_entities` (line 327) `def list_entities(self, case_id)`
- `diff_entities` (line 342) `def diff_entities(self, case_a, case_b)` - *Compare two cases by entity (type, value) keys.

Returns the symmetric difference plus counts and the per-type
breakdown. The "added" set is what case B learned that A did
not have; "removed" is the inverse. This is the OSINT analogue
of `git diff A B` and is the building block for the
"what's new since last run" UI the analyst wants to see.

Both cases are looked up in a single connection hold so the
diff is consistent even if the case store is being written
to concurrently.*
- `search_cases` (line 389) `def search_cases(self, query_substring, limit, query_type)` - *Lightweight case search. LIKE on `query` (not indexed, but
acceptable for the OSINT scale of a few thousand cases).*
- `search_by_entity` (line 419) `def search_by_entity(self, ent_type, value, limit)` - *Find every case that observed a given entity.

This is the cross-run memory query — the heart of "have I
seen this before?".*
- `stats` (line 447) `def stats(self)`
- `_row_to_case` (line 455) `def _row_to_case(self, row)`
- `_safe_json` (line 481) `def _safe_json(text)`
- `_per_type` (line 363) `def _per_type(pairs)`
- `_serialise` (line 369) `def _serialise(pairs)`

#### `change_detection.py`
**Path:** `estorides_core/change_detection.py`
**File Doc:** *estorides_core.change_detection =============================== The temporal layer: "what changed in this investigation since the last run?". Takes two immutable :class:`Snapshot` objects and emits a :class:`ChangeReport` listing every new entity, every disappeared one, every property change, every new source corroboration, and every edge added/removed. Each :class:`Change` carries an auditable ``score`` reliability-weighted via :mod:`reliability_scoring`.  Pure module: no I/O, no DB writes, no logging of payload, no clock. Same input ⇒ same output bit-by-bit. Change ids are content hashes so the case store can deduplicate events across runs.  Public surface::  detect_changes(snapshot_before, snapshot_after, *, config=ChangeConfig()) -> ChangeReport*

**Classes:**
- `Edge` (line 59) `class Edge` - *Outgoing edge: typed destination + relation name.*
- `SnapshotEntity` (line 67) `class SnapshotEntity` - *One entity as captured at snapshot time.

Mirrors the fields the fusion store already persists
(``first_seen``/``last_seen``/``confidence``/``sources``), so a
Snapshot can be built directly from a fused row.*
- `Snapshot` (line 97) `class Snapshot` - *An immutable view of an investigation at one point in time.*
- `ChangeConfig` (line 105) `class ChangeConfig` - *Tuning for :func:`detect_changes`.*
- `Diff` (line 126) `class Diff` - *Structured description of a single change's delta.*
- `Change` (line 135) `class Change` - *One detected change.*
- `ChangeSummary` (line 152) `class ChangeSummary` - *Aggregate stats for a :class:`ChangeReport`.*
- `ChangeReport` (line 169) `class ChangeReport` - *Top-N changes + summary stats for a single diff operation.*

**Methods:**
- `_truncate_key` (line 177) `def _truncate_key(key)`
- `_change_id` (line 184) `def _change_id(kind, entity_id, diff_signature)`
- `_reliability_floor` (line 188) `def _reliability_floor(letter)` - *A=1, B=2, ..., F=6. For 'min_reliability' comparison.*
- `_filter_sources_by_reliability` (line 193) `def _filter_sources_by_reliability(sources, min_reliability)`
- `_property_diff` (line 206) `def _property_diff(before, after)` - *Compute the per-key add/change/remove between two property maps.*
- `_edge_set` (line 225) `def _edge_set(edges)`
- `_union_sources` (line 229) `def _union_sources(a, b)` - *Sorted union of two entities' source lists.

Used in every per-entity Change (property_changed, source_added,
source_removed, edge_added, edge_removed, confidence_shifted) as
the ``sources`` field of the audit trail — the analyst should be
able to see every source that touched the entity, not just the
trigger of the change.*
- `_make_change` (line 241) `def _make_change(kind, entity_id, entity_type, entity_value, sig)` - *Build a :class:`Change` with the deterministic id derived from
``kind + entity_id + sig``. Centralised so the eight change kinds
can never disagree on the field set or the id format.*
- `_below_min_reliability` (line 274) `def _below_min_reliability(source, min_reliability)` - *True if a source's reliability is strictly *worse* than the
configured minimum (i.e. it should be filtered out).*
- `detect_changes` (line 284) `def detect_changes(snapshot_before, snapshot_after)` - *Diff two snapshots. Pure: no I/O, deterministic, bounded.

Parameters
----------
snapshot_before, snapshot_after
    The two snapshots to diff. Either may be ``None``:
    ``before=None`` ⇒ first run, every entity is ``new``;
    ``after=None`` ⇒ no future data, empty report.
config
    Optional tuning. See :class:`ChangeConfig`.*
- `__post_init__` (line 85) `def __post_init__(self)`
- `__post_init__` (line 116) `def __post_init__(self)`

#### `cloud_asset_discovery.py`
**Path:** `estorides_core/cloud_asset_discovery.py`

**Classes:**
- `CloudAsset` (line 34) `class CloudAsset`
- `CloudAssetDiscoveryResult` (line 50) `class CloudAssetDiscoveryResult`
- `CloudAssetDiscoveryError` (line 93) `class CloudAssetDiscoveryError(Exception)`

**Methods:**
- `generate_bucket_names` (line 65) `def generate_bucket_names(domain)`
- `assess_bucket` (line 84) `def assess_bucket(url, method)`
- `to_dict` (line 45) `def to_dict(self)`
- `to_dict` (line 56) `def to_dict(self)`

#### `code_exposure.py`
**Path:** `estorides_core/code_exposure.py`

**Classes:**
- `CodeFinding` (line 47) `class CodeFinding`
- `SeveritySummary` (line 62) `class SeveritySummary`
- `CodeExposureResult` (line 74) `class CodeExposureResult`

**Methods:**
- `validate_aws_key` (line 91) `def validate_aws_key(key)`
- `_is_placeholder` (line 95) `def _is_placeholder(text)`
- `classify_finding` (line 99) `def classify_finding(content, source, file_path)`
- `analyse_findings` (line 189) `def analyse_findings(findings, rate_limited)`
- `to_dict` (line 57) `def to_dict(self)`
- `to_dict` (line 69) `def to_dict(self)`
- `to_dict` (line 81) `def to_dict(self)`

#### `config.py`
**Path:** `estorides_core/config.py`
**File Doc:** *estorides.config ================ Central configuration. Everything tunable lives here.  Two layers coexist on purpose:  * Module-level constants (the original surface). Kept verbatim so the rest of the engine keeps importing `from .config import X` unchanged. * Frozen dataclass config objects (the structured surface) for the v1.3 subsystems: response cache TTL, the recursive pivot engine, the SSE streaming layer and the web tunables. These group related knobs, validate their own bounds at construction and expose a single immutable instance each. No subsystem hard-codes a number; it reads its field off the relevant config object.  Every tunable resolves from an environment variable with a typed, fault-tolerant reader (a malformed env var falls back to the default and logs, instead of crashing the whole process at import time).*

**Classes:**
- `CacheConfig` (line 407) `class CacheConfig` - *Disk response-cache behaviour.*
- `PivotPolicyConfig` (line 420) `class PivotPolicyConfig` - *Which entity types are worth pivoting on, and how leads are scored.

The recursive cross-search expands the highest-scoring leads first.
`type_weights` lets a high-signal selector (an email, a wallet) outrank
a low-signal one (a shared CDN IP) at the same depth. `depth_decay`
discounts every additional hop so the frontier stays close to the seed.*
- `PivotConfig` (line 445) `class PivotConfig` - *Bounds and defaults for the recursive pivot engine.

`*_cap` values are the absolute ceilings applied to any caller-supplied
override (e.g. an API request body), so an untrusted client can never
request an unbounded crawl.*
- `StreamConfig` (line 490) `class StreamConfig` - *Server-Sent-Events streaming knobs (buffer size, cadence).*
- `ReconFusionConfig` (line 502) `class ReconFusionConfig` - *Centralised tunables for the passive recon fusion engine.

Controls how raw OSINT results are grouped, deduplicated and classified
into relevance tiers. Every field has an env var equivalent so the
operator can adjust behaviour without touching code.

This is the SINGLE source of truth. The engine module imports from here
and never defines its own copy.*
- `SchemaConfig` (line 534) `class SchemaConfig` - *Bounds and version for the strict observation/entity data contracts.

`max_str_len` caps any free-form string field (source, category, …).
`max_value_len` caps the identity-bearing entity `value`: it is tighter
than `max_str_len` because a `value` is the merge/identity key used to
derive `canonical_id` downstream, so an oversized blob must be rejected,
not truncated. `max_url_len` is applied as a truncation bound on `meta.url`
(a URL is not identity-bearing; truncating it is a documented, safe bound).
`schema_version` lets consumers detect a contract drift.*
- `WebConfig` (line 561) `class WebConfig` - *Per-endpoint defaults and render limits for the Flask layer.*
- `RetryConfig` (line 576) `class RetryConfig` - *Central retry bounds for HTTP fanout.*

**Functions:**
- `_installed_share` (line 42) `def _installed_share()` - *`<prefix>/share/estorides` — the data-files target of a pip install.*
- `_resolve_dir` (line 47) `def _resolve_dir(env_key, dirname)` - *Resolve a read-only runtime data dir.

Precedence: env var > dev checkout (`PROJECT_ROOT/<dirname>`) >
pip-installed prefix (`<prefix>/share/estorides/<dirname>`, the
`[tool.setuptools.data-files]` target). No I/O besides `is_dir`.*
- `_resolve_writable_dir` (line 63) `def _resolve_writable_dir(env_key, dirname)` - *Resolve a writable runtime dir (no import-time mkdir, issue #49).

Precedence: env var > dev checkout (`PROJECT_ROOT/<dirname>`, when it
exists) > `$XDG_DATA_HOME/estorides/<dirname>` (default
`~/.local/share/estorides/<dirname>`). A pip-installed wheel must
never write into site-packages.*
- `ensure_data_dirs` (line 101) `def ensure_data_dirs()` - *Idempotently create DATA_DIR.

Replaces the import-time mkdir that issue #49 reported. Call
this from the app factory and from any code path that actually
needs to write to DATA_DIR. Idempotent: exist_ok=True so a
pre-existing directory is fine.*
- `ensure_reports_dir` (line 112) `def ensure_reports_dir()` - *Idempotently create REPORTS_DIR.

Same posture as ensure_data_dirs(). /api/export/<fmt> used to
write through this path; issue #43 moved export artefacts to a
tempfile, but the directory is still useful for operator-facing
exports.*
- `_env_tool_allowlist` (line 169) `def _env_tool_allowlist()`
- `contact_level` (line 247) `def contact_level(contact)` - *Map a contact class to its numeric severity, unknown values to active.

An unrecognised class is treated as the most exposing (`active`) so a
typo in a source YAML can never silently downgrade an operator's
passive-only guarantee.*
- `effective_proxies` (line 273) `def effective_proxies(explicit)` - *Resolve the proxy rotation pool from an explicit value or the env.

Precedence: an explicit caller value (CLI flag) wins; otherwise the
pool env var, otherwise the single-proxy env var. Returns an empty
list when no anonymising egress is configured.*

**Methods:**
- `_pivot_weight_map` (line 591) `def _pivot_weight_map()` - *Default per-type lead weights for the pivot scorer.

Strong, single-owner selectors rank above shared infrastructure.*
- `_csv_frozenset` (line 608) `def _csv_frozenset(name, default)` - *Read a comma-separated env var into a frozenset, else the default.*
- `retry_delay` (line 713) `def retry_delay(attempt)` - *Return the backoff sleep for a 1-indexed attempt.*
- `is_active` (line 414) `def is_active(self)` - *Cache is only consulted when enabled and the TTL is positive.*
- `is_pivotable` (line 434) `def is_pivotable(self, entity_type)` - *True when an entity of `entity_type` should be re-queried.*
- `lead_score` (line 438) `def lead_score(self, entity_type, depth, parent_score)` - *Priority of expanding this lead. Higher expands sooner.*
- `clamp_depth` (line 468) `def clamp_depth(self, value)` - *Clamp a requested depth into [1, max_depth_cap].*
- `clamp_steps` (line 472) `def clamp_steps(self, value)` - *Clamp a requested step budget into [1, max_steps_cap].*
- `clamp_entities` (line 476) `def clamp_entities(self, value)` - *Clamp a requested entity budget into [1, max_entities_cap].*
- `clamp_parallel` (line 480) `def clamp_parallel(self, value)` - *Clamp a requested fan-out width into [1, parallel_cap].*
- `clamp_deadline` (line 484) `def clamp_deadline(self, value)` - *Clamp a requested per-target deadline into (0, deadline_cap_seconds].*
- `__post_init__` (line 523) `def __post_init__(self)`
- `delay` (line 584) `def delay(self, attempt)` - *Exponential backoff for 1-indexed attempt, clamped to cap.*

#### `discoverer.py`
**Path:** `estorides_core/discoverer.py`
**File Doc:** *Background subdomain/domain discoverer.  Drops a seed and walks the passive attack surface recursively. As of v1.3 the recursion itself lives in `pivot_engine.PivotEngine`; this module is the thin adapter that:  * owns the long-lived `DiscoverJob` registry the SSE/CLI layers poll, * drives the engine with the infrastructure-only pivot policy (domains and IPs, the historical discoverer surface), and * translates engine `PivotEvent`s into the legacy event dicts the UI and CLI already understand (`started`, `step_start`, `node_found`, `step_done`, `finished`, `error`, ...).  Keeping one engine and many adapters is the DRY win: the deep-run SSE endpoint and this background discoverer share identical recursion, scoring and budgeting code; only the policy and the event shape differ.  Design guarantees preserved from v1.2:  * No new HTTP layer — the engine reuses the existing `Orchestrator`. * Persistence is via the case store; every hop appends to one case. * Bounded: depth, steps and entity ceilings come from config caps. * Idempotent: re-queuing the same (type, value) is a no-op (engine).*

**Classes:**
- `DiscoverJob` (line 50) `class DiscoverJob` - *One background discovery session.

Lives in `DISCOVER_JOBS` keyed by job_id; survives the SSE handler
exiting (so a UI reload reconnects and resumes).*
- `_DiscoverJobSink` (line 95) `class _DiscoverJobSink` - *Adapts engine `PivotEvent`s to the legacy DiscoverJob event dicts.

The UI and CLI consume `step_start`, `node_found`, `step_done`,
`finished` and `error`; this translator preserves those shapes so the
engine swap is invisible to every existing consumer.*

**Methods:**
- `_new_job_id` (line 189) `def _new_job_id()` - *Monotonic-ish id with a timestamp prefix for natural sort.*
- `create_discover_job` (line 194) `def create_discover_job(seed_type, seed_value)` - *Create and register a discovery job synchronously.

This does only fast, loop-free work (a case-store insert and some
bookkeeping), so it is safe to call straight from a Flask request
thread. The asyncio worker that actually crawls is scheduled
separately by `start_discover` / `start_discover_threadsafe`, so the
caller never blocks on the shared background loop.*
- `start_discover` (line 252) `def start_discover(seed_type, seed_value)` - *Create a discovery job and schedule its worker on the current loop.

Kept as the coroutine entry point for callers that already own a
running loop (the CLI). Web callers use `start_discover_threadsafe`,
which never blocks the request thread on the background loop.*
- `start_discover_threadsafe` (line 285) `def start_discover_threadsafe(loop, seed_type, seed_value)` - *Create the job in the calling thread, fire its worker on `loop`.

Returns immediately. The worker is queued with
`run_coroutine_threadsafe` and runs whenever the loop is next free, so
a busy loop (a concurrent deep-run) can never make this call time out.*
- `_run_discoverer` (line 319) `def _run_discoverer(job)` - *The background loop. One asyncio task per job, driving the engine.*
- `list_jobs` (line 349) `def list_jobs(limit)` - *Snapshot of the recent jobs for the /api/discover/jobs endpoint.*
- `stop` (line 78) `def stop(self)`
- `should_stop` (line 81) `def should_stop(self)`
- `push_event` (line 84) `def push_event(self, ev)` - *Append an event and keep the buffer bounded.*
- `__init__` (line 103) `def __init__(self, job)`
- `emit` (line 106) `def emit(self, event)`
- `_on_started` (line 113) `def _on_started(self, data)`
- `_on_target_start` (line 116) `def _on_target_start(self, data)`
- `_on_entity` (line 126) `def _on_entity(self, data)`
- `_on_target_done` (line 141) `def _on_target_done(self, data)`
- `_on_target_error` (line 153) `def _on_target_error(self, data)`
- `_on_stopping` (line 160) `def _on_stopping(self, data)`
- `_on_finished` (line 163) `def _on_finished(self, data)`
- `_on_fatal` (line 174) `def _on_fatal(self, data)`

#### `entity_extraction.py`
**Path:** `estorides_core/entity_extraction.py`
**File Doc:** *estorides_core.entity_extraction ================================ Single source of truth for finding entities (IPs, domains, emails, CVEs, crypto addresses, etc.) anywhere in any JSON / text response.  This is the function that turns a wall of OSINT JSON into nodes the knowledge graph can wire up.*

**Classes:**
- `Entity` (line 22) `class Entity`

**Methods:**
- `_looks_like_phone` (line 49) `def _looks_like_phone(query)` - *True for E.164-ish numbers with 7-15 digits (never consumes hashes).*
- `normalize_query` (line 57) `def normalize_query(query)` - *Return the routing form of raw operator input.

URLs reduce to their lowercased host (userinfo, port, path, query
and fragment stripped); `@handles` reduce to the bare handle;
everything else is trimmed verbatim.*
- `detect_query_type` (line 100) `def detect_query_type(query)` - *Return the detected type of a free-form query.

Falls back to 'domain' for anything that looks like a hostname
(contains a dot, no spaces), and to 'keyword' for everything else.*
- `_is_valid_domain` (line 127) `def _is_valid_domain(candidate)`
- `_context` (line 140) `def _context(text, start, end, window)`
- `extract_from_text` (line 146) `def extract_from_text(text, source)` - *Find every recognised entity in a raw text blob.

`types` optionally restricts which kinds of entity to look for.*
- `_ip_in_textual_context` (line 200) `def _ip_in_textual_context(text, idx)` - *Heuristic: only count a numeric match as an IP if it isn't part of a
version number or timestamp (preceded/followed by `version`, `v`, or `:`).*
- `extract_from_json` (line 210) `def extract_from_json(payload, source)` - *Pull entities out of a JSON-like structure.

Earlier this recursed into every string and ran all patterns on each one,
so a response with thousands of entries (crt.sh subdomains, wayback URLs)
triggered hundreds of thousands of regex passes. We now flatten the payload
to a single capped string and scan it once; entities span keys and values
just as well, and the cost is bounded by `extract_from_text`.*
- `_clean_scalar` (line 278) `def _clean_scalar(value)` - *Return a stripped string for a scalar leaf, or None for non-scalars.*
- `_looks_like_person` (line 288) `def _looks_like_person(value)` - *True when a value reads like a human name (has a space, mostly letters).*
- `_looks_like_username` (line 299) `def _looks_like_username(value)` - *True when a value reads like a handle (no spaces, handle charset).*
- `_classify_keyed_value` (line 306) `def _classify_keyed_value(key, value)` - *Map a (key, scalar value) pair to a human-selector entity type, or None.*
- `extract_structured` (line 330) `def extract_structured(payload, source)` - *Extract human selectors (email, username, person, org, phone) by key.

Walks the JSON structure and types values by the key they sit under,
which is the only reliable way to recover usernames and person names
(they have no lexical signature for a regex to catch). Bounded by
`ENTITY_MAX_PER_TYPE` per type and a node-visit cap so a pathological
response cannot turn this into a CPU stall.*
- `merge` (line 382) `def merge()` - *Deduplicate by (type, value) and merge sources / contexts.

Two entities with the same (type, value) collapse into a single
record. Their `sources` lists are unioned (preserving the first
seen order), `context` is concatenated up to a 200-char window,
and `confidence` is bumped by 0.1 for each extra observation
(capped at 1.0) — a simple "corroboration bonus".

The `sources` field is the canonical "all the places this was
seen" record. The previous implementation stored it in a
private attribute (`_src`) that was never serialised and broke
`asdict()` (TypeError on `dataclasses.asdict` for non-dataclass
attributes). This version is a real, declared field.

v1.1: After exact-key dedup, run a second pass with
`difflib.SequenceMatcher` to catch near-misses like
`EvilCorp.com` vs `evil-corp.com`. Anything above the
`FUZZY_THRESHOLD` ratio collapses into a single record.
Returns the deduped list, with cluster groups available via
`fuzzy_clusters` if the caller wants them.

v1.4: pass ``fuzzy=False`` to do exact-key dedup only. The
orchestrator does this when the canonical identity layer
(`entity_resolution`) is active, so the resolver owns every fuzzy
decision instead of inheriting a coarser difflib pre-merge that would,
for example, silently fuse two distinct domain registrations the
resolver intends to keep separate and merely *link*.*
- `_fuzzy_cluster` (line 476) `def _fuzzy_cluster(entities)` - *Group entities of the same type by string similarity and merge.

Uses `difflib.SequenceMatcher.ratio()`. We compare normalised
(lowercased, hyphen-stripped) forms so `evil-corp.com` and
`evilcorp.com` collide cleanly. Only domain / email / person /
org types are eligible — IPs, hashes, and CVEs have exact
semantics where fuzzy would be a bug, not a feature.*
- `to_dict` (line 34) `def to_dict(self)`
- `visit` (line 344) `def visit(node, key)`
- `find` (line 499) `def find(x, parent)`
- `union` (line 505) `def union(a, b, parent)`
- `norm` (line 510) `def norm(v)`

#### `entity_resolution.py`
**Path:** `estorides_core/entity_resolution.py`
**File Doc:** *estorides_core.entity_resolution ================================ Canonical identity layer.  The v1.1 engine collapsed near-duplicate entities with a single ``difflib.SequenceMatcher`` ratio at 0.85. That is fine for typo folding but it is not entity resolution: it has no stable identifier, no blocking (so it is quietly O(n^2) over the whole entity list), no notion of which match was deterministic versus a guess, and no cross-script awareness, so ``Владимир Путин`` and ``Vladimir Putin`` stay two unrelated nodes.  This module is the replacement. For a per-run list of :class:`Entity` records it produces a list of :class:`CanonicalEntity` records, each with:  * a **stable canonical id** derived from a type-aware normalised form, so the same real-world entity gets the same id on every run (and, with the persistent store, across runs); * **blocked, scored matching** — exact normalised equality is a deterministic merge; close-but-not-equal pairs inside the same blocking bucket are scored with Jaro-Winkler (and a cross-script consonant skeleton booster) and either merged or surfaced as a ``SAME_AS`` candidate link, never silently fused; * **provenance** — the sources, original surface forms (aliases), scripts, and the match method/score that justified each merge.  Deterministic types (IP, hash, CVE, ASN, crypto address) never fuzzy match: for those, equality of the normalised form is the only merge rule, because a one-character difference is a different host, file, or wallet.*

**Classes:**
- `MatchScore` (line 295) `class MatchScore` - *The result of comparing two entity values of the same type.*
- `CanonicalEntity` (line 341) `class CanonicalEntity` - *A resolved identity fused from one or more observed entities.*
- `SameAsLink` (line 401) `class SameAsLink` - *A suggested-but-unmerged identity link between two canonical ids.*
- `ResolutionResult` (line 419) `class ResolutionResult` - *Output of a resolve() call: fused entities plus candidate links.*
- `_UnionFind` (line 432) `class _UnionFind` - *Disjoint-set over integer indices with path compression.*
- `EntityResolver` (line 454) `class EntityResolver` - *Resolve a per-run entity list into canonical identities.

The resolver is deliberately stateless across calls by default; pass a
store (see :mod:`estorides_core.entity_store`) to make canonical ids
stable across runs. A single :meth:`resolve` call:

1. exact-merges entities sharing a ``(type, normalized)`` key;
2. blocks the remaining fuzzy-eligible groups and scores in-bucket
   pairs, unioning clusters at or above the merge threshold and
   recording ``SAME_AS`` candidates above the link threshold;
3. emits one :class:`CanonicalEntity` per cluster with a deterministic
   representative and full provenance.*

**Functions:**
- `jaro` (line 83) `def jaro(s1, s2)` - *Return the Jaro similarity of two strings in ``[0, 1]``.*
- `jaro_winkler` (line 126) `def jaro_winkler(s1, s2, prefix_weight)` - *Jaro-Winkler similarity: Jaro with a shared-prefix bonus.

The prefix bonus (up to 4 leading characters) rewards strings that
agree at the start, which is the common shape of name and domain
variants. ``prefix_weight`` is capped at 0.25 to keep the result in
``[0, 1]``.*
- `_soundex` (line 146) `def _soundex(token)` - *Return a 4-character Soundex code for a Latin token.

Used only as a blocking key: it groups phonetically similar tokens so
the expensive pairwise scorer runs on plausible candidates rather than
the whole list. Empty or non-alphabetic input yields ``"0000"``.*
- `_normalize_domain` (line 178) `def _normalize_domain(value)`
- `_normalize_name` (line 187) `def _normalize_name(value)` - *Order-independent transliterated key for persons and orgs.

The surface form is transliterated to Latin, punctuation dropped, and
the tokens sorted, so ``"Putin, Vladimir"`` and ``"Владимир Путин"``
converge on the same normalised string and merge deterministically.
Cross-script spellings that survive transliteration with different
vowels (abjad scripts) are caught later by the skeleton scorer.*
- `normalize_value` (line 206) `def normalize_value(etype, value)` - *Return the canonical normalised form of an entity value.

The normalised form is the merge key for deterministic types and the
seed of the stable canonical id for every type. It is intentionally
lossy (case, ordering, ornament removed) but never collapses distinct
objects of a deterministic type.*
- `canonical_id` (line 246) `def canonical_id(etype, normalized)` - *Stable, content-addressed id for a normalised entity.

Same ``(type, normalized)`` always yields the same id, so a canonical
entity keeps its identity for as long as its normalised form is stable.
The persistent store additionally maps known aliases onto an existing
id so a never-before-seen surface form still resolves to the same node.*
- `blocking_keys` (line 257) `def blocking_keys(etype, normalized, value)` - *Return the blocking keys that bucket an entity for comparison.

Two entities are only ever scored against each other if they share at
least one blocking key. The keys are chosen to be high-recall (so true
matches land together) while keeping buckets small enough that the
in-bucket pairwise scan stays cheap.*

**Methods:**
- `score_pair` (line 302) `def score_pair(etype, a_value, b_value, a_norm, b_norm)` - *Score how likely two same-type entities denote the same object.

Deterministic types return 1.0 only on normalised equality and 0.0
otherwise. Fuzzy types layer Jaro-Winkler over the normalised form with
a cross-script consonant-skeleton booster for names, so a Cyrillic and
a Latin spelling of one person can clear the link bar even when their
transliterated vowels differ.*
- `_script_of` (line 450) `def _script_of(value)`
- `resolve_entities` (line 727) `def resolve_entities(entities)` - *Module-level convenience wrapper around :class:`EntityResolver`.*
- `to_dict` (line 357) `def to_dict(self)`
- `to_entity` (line 373) `def to_entity(self)` - *Project back onto the legacy :class:`Entity` shape.

Lets the resolver drop into the existing orchestrator, knowledge
graph, and case-store paths without changing their interfaces while
carrying the new identity metadata in ``attributes``.*
- `to_dict` (line 409) `def to_dict(self)`
- `to_dict` (line 425) `def to_dict(self)`
- `__init__` (line 435) `def __init__(self, n)`
- `find` (line 438) `def find(self, x)`
- `union` (line 444) `def union(self, a, b)`
- `__init__` (line 469) `def __init__(self)`
- `resolve` (line 482) `def resolve(self, entities)` - *Resolve ``entities`` into canonical identities and links.*
- `_build_records` (line 498) `def _build_records(self, entities)`
- `_exact_merge` (line 513) `def _exact_merge(records, uf)`
- `_fuzzy_merge` (line 525) `def _fuzzy_merge(self, records, uf)` - *Block, score, merge at threshold, and collect link candidates.

Merges (union) happen eagerly as pairs clear the merge bar. Pairs
that only clear the lower link bar are returned as ``(a, b, score)``
index triples for the caller to translate into canonical-id links
once clusters are materialised.*
- `_build_links` (line 566) `def _build_links(candidates, uf, root_to_cid)` - *Translate index-pair link candidates into canonical-id links.

Pairs whose clusters ended up merged (a later, stronger edge pulled
them together) are dropped; the rest are deduplicated per
canonical-id pair, keeping the highest-scoring justification.*
- `_materialise` (line 594) `def _materialise(self, records, uf)`
- `_representative` (line 661) `def _representative(members)` - *Pick a stable representative for a cluster.

Preference order: most corroborated (appears in most sources), then
a Latin surface form over a non-Latin one (more broadly readable in
reports), then the lexicographically smallest value. The result is
deterministic for a given member set, which keeps the canonical id
stable run to run.*
- `_best_internal_match` (line 679) `def _best_internal_match(members)` - *Return the (method, score) of the strongest non-exact pair.

Surfaces *why* a multi-member cluster was fused, so the analyst can
see whether the merge rested on an exact key or a probabilistic
name match. Bounded to the first handful of members to stay cheap on
large clusters.*
- `_reconcile_with_store` (line 705) `def _reconcile_with_store(self, canonicals)` - *Map canonicals onto persisted ids and record their aliases.

Looked up alias-first so a brand-new surface form of a known entity
adopts the existing canonical id instead of minting a new one. The
store call is best-effort: any failure leaves the freshly computed
id in place rather than aborting the run.*
- `rank` (line 670) `def rank(rec)`

#### `entity_store.py`
**Path:** `estorides_core/entity_store.py`
**File Doc:** *estorides_core.entity_store =========================== Cross-run canonical identity store.  The resolver in :mod:`estorides_core.entity_resolution` produces stable canonical ids *within* a run from content-addressed normalised forms. That is not enough for state-level work, where the same target recurs across investigations: a person seen as ``Vladimir Putin`` in one case and only as ``Владимир Путин`` in the next must keep one identifier so the graph, watchlists, and case history all join on it.  This store closes that gap. It persists every canonical entity and *all* the surface forms (aliases) ever observed for it, each indexed by its normalised key. On a later run the resolver asks :meth:`lookup` whether any of a freshly computed entity's normalised forms — its own or any of its aliases — has been seen before, and if so adopts the established canonical id instead of minting a new one.  It is a thin SQLite layer, matching :mod:`estorides_core.cases`: WAL mode, a single serialised connection behind a lock, and a fail-soft contract — the resolver treats any store error as "no prior knowledge" and proceeds, so a read-only or missing data directory degrades to in-run resolution rather than breaking a run.*

**Classes:**
- `EntityStore` (line 60) `class EntityStore(SqliteStore)` - *Thread-safe SQLite repository of canonical identities and aliases.*

**Methods:**
- `open_store` (line 155) `def open_store(path)` - *Open the store, returning None instead of raising on failure.

The resolver calls this so that an unwritable or locked data directory
degrades the cross-run identity feature to in-run-only resolution rather
than aborting an investigation.*
- `lookup` (line 66) `def lookup(self, etype, normalized, aliases)` - *Return an existing canonical id for any known form, or None.

Checks every normalised key the entity can present — its own and
each alias's — against both the canonical ``entities`` table and the
``aliases`` table. The first established id wins, so a never-before
spelling of a known target still resolves to its prior identity.*
- `upsert` (line 104) `def upsert(self, entity)` - *Persist (insert or update) a canonical entity and its aliases.

``first_seen`` is preserved across updates; ``last_seen``,
``member_count``, ``source_count``, ``value``, and ``confidence``
track the latest resolution. Every alias is recorded with its
normalised key so future lookups can route any surface form back to
this id.*
- `stats` (line 143) `def stats(self)` - *Return a one-glance summary of store size.*

#### `event_bus.py`
**Path:** `estorides_core/event_bus.py`
**File Doc:** *estorides_core.event_bus ======================== In process publish subscribe channel for orchestrator lifecycle events.  I let metrics, SSE and future plugins observe runs without the orchestrator importing them. Delivery is synchronous and total: a failing subscriber never breaks a run and never mutates the payload seen by the next subscriber.*

**Classes:**
- `EventBus` (line 24) `class EventBus` - *Synchronous in memory event channel with isolated delivery.*

**Methods:**
- `get_bus` (line 85) `def get_bus()` - *Return the process wide shared bus instance.*
- `__init__` (line 27) `def __init__(self)` - *Create an empty bus with no subscribers.*
- `subscribe` (line 31) `def subscribe(self, event, handler)` - *Register handler for event and return it for unsubscription.*
- `unsubscribe` (line 41) `def unsubscribe(self, event, handler)` - *Remove handler from event and report whether it was present.*
- `publish` (line 50) `def publish(self, event, payload)` - *Deliver a copy of payload to each subscriber and count attempts.*
- `clear` (line 63) `def clear(self, event)` - *Remove subscribers for one event or for the whole bus.*
- `subscriber_count` (line 71) `def subscriber_count(self, event)` - *Return the number of handlers registered for event.*
- `_check_event` (line 76) `def _check_event(self, event)` - *Validate that an event name uses the constrained alphabet.*

#### `feeds.py`
**Path:** `estorides_core/feeds.py`
**File Doc:** *estorides_core.feeds ==================== Real-time intelligence feeds, polled and cached locally.  This is the "osiris map layers" feature, brought to the Python side: each feed is a small object that knows how to fetch, parse, and normalise one external signal (flights, earthquakes, fires, news, sanctions) into a common GeoPoint shape ready for the map.  Why a separate module instead of just YAML sources? * The feeds are polled, not queried. They don't take a user query; they take a bounding box or a time window. * They produce spatiotemporal data (lat, lon, timestamp) which doesn't fit the source-parser model. * They have a much higher refresh rate (15-60 min) so we cache aggressively to disk and short-circuit on the same minute.  Each feed exposes a uniform API: * `name`  — slug used by the API * `description` — human-readable one-liner * `fetch(bbox=None)` — list[dict] of normalised records * `point(record)` — (lat, lon) tuple, or None  Records are plain dicts with at least: {lat, lon, timestamp, label, source}.*

**Classes:**
- `FeedPoint` (line 54) `class FeedPoint` - *Normalised point for the map layer.

`kind` discriminates the marker on the map (flight / quake / fire /
news / vessel). `extra` is an open dict for source-specific fields
the frontend may want to render in the popup.*
- `Feed` (line 74) `class Feed(ABC)` - *Base class for a real-time feed.*
- `EarthquakesFeed` (line 148) `class EarthquakesFeed(Feed)` - *USGS M2.5+ earthquakes, last 24h, worldwide.*
- `FiresFeed` (line 190) `class FiresFeed(Feed)` - *NASA FIRMS active fire hotspots (VIIRS_NOAA20_NRT, last 24h).

FIRMS retired its keyless CSV download in 2024. The new endpoint
at `api/data/active_fire/csv/...` requires a MAP_KEY (free with
NASA Earthdata registration) which is read from the
`ESTORIDES_FIRMS_KEY` env var. If the key is missing the feed
silently returns zero points and logs a warning — it never
breaks the rest of the platform.*
- `NewsFeed` (line 251) `class NewsFeed(Feed)` - *GDELT 2.0 — global news articles (article-list API).

The GeoJSON endpoint we used in the previous version was retired.
The current keyless endpoint is the article-list API at
`/api/v2/doc/doc` which returns a JSON `articles` array. We
attempt to geocode the article URL via the embedded
`socialimage` or `domain` field when coordinates aren't in the
payload — falling back to the article's source domain
coordinates is intentionally out of scope for this version, so
a record without explicit coordinates is dropped.*

**Methods:**
- `list_feeds` (line 313) `def list_feeds()` - *Return public feed descriptions for the /api/feeds endpoint.*
- `get_feed` (line 321) `def get_feed(name)`
- `fetch_all` (line 325) `def fetch_all(bbox, use_cache)` - *Fetch every registered feed (optionally clipped to a bbox).

Used by /api/feeds to populate the map in one round-trip.

bbox = (min_lon, min_lat, max_lon, max_lat). Points outside the
bbox are dropped before the response is returned.*
- `to_dict` (line 69) `def to_dict(self)`
- `__init__` (line 82) `def __init__(self)`
- `_fetch` (line 87) `def _fetch(self)` - *Subclass implementation: hit the upstream and return points.*
- `fetch` (line 90) `def fetch(self)` - *Public entrypoint. Reads/writes the on-disk cache.

The cache is a simple `{fetched_at, points}` JSON blob —
good enough for a self-hosted deployment. A real production
system would want a TTL'd Redis layer, but that pulls in a
network dependency.*
- `point` (line 135) `def point(self, record)` - *Default: return (lat, lon) if both present.*
- `_fetch` (line 156) `def _fetch(self)`
- `_fetch` (line 209) `def _fetch(self)`
- `_fetch` (line 271) `def _fetch(self)`

#### `fusion_analytics.py`
**Path:** `estorides_core/fusion_analytics.py`
**File Doc:** *estorides_core.fusion_analytics =============================== Intelligence analytics layer over the fusion store.  Provides the "dashboard" queries that Palantir's Athena delivers: multi-source consensus, entity timelines, source reliability stats, and corroboration analytics. All read-only, all parameterized SQL, all fail-soft.  Public surface: analytics = FusionAnalytics(fusion_store_instance) analytics.entity_timeline(eid) -> dict | None analytics.entity_summary(eid) -> dict | None analytics.source_stats(source_name) -> dict | None analytics.multi_source_consensus(eid, key) -> dict analytics.corroborated_properties(eid, min_sources=2) -> list analytics.entity_search(term, ...) -> list analytics.top_changed(days=7, limit=20) -> list analytics.source_corroboration_matrix(limit=20) -> list*

**Classes:**
- `FusionAnalytics` (line 31) `class FusionAnalytics` - *Read-only analytics queries over the fusion store.

Every method is parameterized SQL — no string interpolation.
A ``store`` of ``None`` is silently handled (all methods return
empty results) so the caller never needs to guard against a
missing datastore.*

**Methods:**
- `__init__` (line 40) `def __init__(self, store)`
- `entity_timeline` (line 46) `def entity_timeline(self, eid)`
- `entity_summary` (line 132) `def entity_summary(self, eid)`
- `source_stats` (line 213) `def source_stats(self, source_name)`
- `multi_source_consensus` (line 290) `def multi_source_consensus(self, eid, key)`
- `corroborated_properties` (line 343) `def corroborated_properties(self, eid, min_sources)`
- `entity_search` (line 374) `def entity_search(self, term, etype)`
- `top_changed` (line 432) `def top_changed(self, days, limit)`
- `source_corroboration_matrix` (line 477) `def source_corroboration_matrix(self, limit)`
- `_resolve_entity_value` (line 509) `def _resolve_entity_value(self, eid)`
- `_resolve_entity_type` (line 520) `def _resolve_entity_type(self, eid)`
- `_intel_level` (line 532) `def _intel_level(source_count, sources)`
- `_deduplicate_relationships` (line 540) `def _deduplicate_relationships(rels)`

#### `fusion_store.py`
**Path:** `estorides_core/fusion_store.py`
**File Doc:** *estorides_core.fusion_store =========================== The data-fusion datastore — the single place where everything every source (loaded from the YAML catalogue) ever produced is fused, cross-run, into one queryable intelligence picture.  Why this exists --------------- The case store (:mod:`estorides_core.cases`) keeps a *per-run silo*: each investigation writes its own copy of the observations and entities it saw, keyed by ``case_id``. The same real-world entity seen across fifty runs becomes fifty rows, and nothing in the relational store answers "show me everything we know about X, from every source, across every case".  The fusion store closes that gap. It is the relational analogue of the Kùzu graph: a normalised, deduplicated, source-attributed fact base that accumulates across runs. The fusion is provenance-preserving — when two sources corroborate the same entity or property, the record is merged but *every* contributing source is retained, so confidence is grounded in how many independent feeds agree.  Schema (five tables) -------------------- fusion_sources         the YAML source catalogue, with fetch counters fusion_entities        canonical, cross-run, deduplicated entities fusion_entity_sources  which sources corroborated each entity (provenance) fusion_observations    every source response, fused across runs fusion_properties      attribute key/value per entity, attributed to source fusion_relationships   edges between entities, attributed to source*

**Classes:**
- `FusionStore` (line 190) `class FusionStore(SqliteStore)` - *Thread-safe SQLite-backed fusion datastore.

One serialised connection guarded by a lock, WAL journalling, and
``ON DELETE CASCADE`` foreign keys so dropping an entity reaps its
provenance, properties and edges.*

**Functions:**
- `entity_id` (line 177) `def entity_id(etype, value, normalized)` - *Deterministic, run-independent id for an entity.

Derived from the type and the normalised value so the same real-world
entity always hashes to the same id no matter which run or source first
produced it — the property that makes cross-run fusion automatic.*

**Methods:**
- `open_store` (line 723) `def open_store(path)` - *Open the fusion store, returning None instead of raising on failure.

The orchestrator calls this so an unwritable or locked data directory
degrades the fusion layer to a no-op rather than aborting an
investigation.*
- `_ensure_entity_stub` (line 202) `def _ensure_entity_stub(conn, etype, value)` - *Insert a minimal entity row if absent and return its id.

Used for relationship endpoints (a country, an ASN, a port) that no
entity-extraction pass produced, so an edge is always navigable from
both ends. Runs inside the caller's transaction; never overwrites an
already-fused entity (``INSERT OR IGNORE``).*
- `register_sources` (line 222) `def register_sources(self, sources)` - *Mirror the YAML source catalogue into the store.

Idempotent: an existing source keeps its ``first_seen`` and its
accumulated counters; only the descriptive columns are refreshed so
the catalogue tracks edits to the YAML without losing fetch history.*
- `add_observation` (line 259) `def add_observation(self, observation)` - *Fuse a single source response into the cross-run observation log
and bump the source's fetch/ok counters.*
- `fuse_entity` (line 300) `def fuse_entity(self, entity)` - *Fuse one entity into the canonical store and return its id.

The ``(type, normalized)`` pair is the dedup key. On a repeat sighting
the row's ``last_seen``, confidence (Bayesian merge), and observation
count advance, and every contributing source is recorded in
``fusion_entity_sources`` so provenance survives the merge.

Confidence uses the :mod:`reliability_scoring` pipeline: source
reliability, source type hierarchy, and corroboration count are
factored into the score instead of a raw ``MAX()``. A tertiary or
unreliable source cannot override a well-corroborated primary one.*
- `fuse_entities` (line 403) `def fuse_entities(self, entities)` - *Fuse a batch of entities, returning the list of fused ids.*
- `fuse_properties` (line 416) `def fuse_properties(self, eid, parsed, source)` - *Fuse the flat scalar attributes of a parsed observation onto an
entity, attributed to ``source``.

Only one nesting level of scalar values is taken: deep structures are
the observation payload's job, not an entity attribute's. Returns the
number of properties written. The ``(entity_id, key, value, source)``
primary key means re-running the same source is idempotent while a
*different* source asserting the same key/value adds corroboration.*
- `fuse_relationship` (line 462) `def fuse_relationship(self, src_type, src_value, relation, dst_type, dst_value)` - *Fuse one directed edge between two entities, attributed to source.

Both endpoints are resolved to their deterministic fusion ids so the
edge joins the same canonical entities the entity table holds.
Confidence uses the :mod:`reliability_scoring` pipeline instead of a
raw ``MAX()``, so a low-reliability source cannot inflate the score
of a well-corroborated edge.*
- `fuse_graph` (line 532) `def fuse_graph(self, kg)` - *Mirror the analytic edges of a knowledge graph into the store.

Reads node ``type``/``value`` off each endpoint and skips the pure
plumbing relations (``observed_by``, ``co_occurs``, ``mentions``) and
any edge touching a ``source`` node, so only pivot-worthy facts land.
Returns the number of edges fused. Best-effort: a malformed graph is
swallowed rather than aborting a run.*
- `get_entity` (line 568) `def get_entity(self, eid)` - *Return one fused entity with its provenance, properties and edges.*
- `search_entities` (line 614) `def search_entities(self, term, etype)` - *Search fused entities by value substring and/or type.

``min_sources`` filters to entities corroborated by at least N feeds —
the fusion-native "only show me what more than one source agrees on"
query. Ordered by source breadth then recency.*
- `corroborated_properties` (line 657) `def corroborated_properties(self, eid, min_sources)` - *Return an entity's properties that at least ``min_sources`` distinct
feeds independently asserted — the fusion store's confidence signal.*
- `list_sources` (line 674) `def list_sources(self, limit)` - *Return the source catalogue with accumulated fetch/ok counters.*
- `stats` (line 692) `def stats(self)` - *One-glance dashboard of the fused store's size.*
- `normalize_value` (line 73) `def normalize_value(etype, value)`
- `_count` (line 695) `def _count(table)`

#### `graph_bundle.py`
**Path:** `estorides_core/graph_bundle.py`
**File Doc:** *graph_bundle: payload circle 2D + sphere 3D estilo ReadMenator.  Porta ``readmenator/_bundlegraph.py`` (``BundleGraphRenderer.build_payload``) y ``readmenator/_graphlayout.py`` (``hierarchical_edge_bundling`` + ``spherical_edge_bundling`` + Fibonacci + B-spline, Holten 2006) al grafo OSINT de Estorides.  Puro y sin I/O: consume el payload RAW de ``estorides_core.graph_force.build_force_payload`` y devuelve el payload bundle ``{nodes, groups, edges}`` con la misma forma que ReadMenator (para que el JS porteado sea casi verbatim). Todo input hostil se valida fail-closed (``TypeError``) o se trunca a limites fijos. Nunca genera HTML.*

**Classes:**
- `BundleLayout` (line 37) `class BundleLayout` - *Resultado del bundling circular (igual que ReadMenator).*
- `SphereBundleLayout` (line 49) `class SphereBundleLayout` - *Resultado del bundling esferico (igual que ReadMenator).*

**Methods:**
- `_req_str` (line 59) `def _req_str(item, key, default, what)`
- `_opt_str` (line 68) `def _opt_str(item, key, default)`
- `_opt_float` (line 73) `def _opt_float(item, key, default)`
- `_opt_int` (line 84) `def _opt_int(item, key, default)`
- `_opt_community` (line 94) `def _opt_community(value)`
- `_bspline` (line 102) `def _bspline(control, samples)` - *Muestrea una B-spline cubica uniforme clamped (cualquier dimension).*
- `_bundle_curves` (line 135) `def _bundle_curves(leaves, member_group, hub, root, edges, beta, samples)` - *Rutea cada arista por la jerarquia y la muestrea como B-spline.*
- `hierarchical_edge_bundling` (line 171) `def hierarchical_edge_bundling(groups, edges, center, radius, beta, samples, group_gap, inner_ratio)` - *Hojas en un circulo por grupo; aristas por la jerarquia (Holten 2006).*
- `fibonacci_sphere` (line 215) `def fibonacci_sphere(count)` - *Vectores unitarios casi uniformes en espiral de angulo dorado.*
- `_unit` (line 231) `def _unit(v)`
- `_split_caps` (line 238) `def _split_caps(labels, sizes, points, lattice, out)` - *Biseca el lattice entre runs de grupos hasta un cap contiguo por grupo.*
- `spherical_edge_bundling` (line 267) `def spherical_edge_bundling(groups, edges, radius, beta, samples, inner_ratio)` - *Hojas en caps esfericos por comunidad; aristas por la jerarquia.*
- `_round2` (line 316) `def _round2(pt)`
- `_round3` (line 320) `def _round3(pt)`
- `bundle_settings` (line 324) `def bundle_settings()` - *Defaults de la pagina bundle (fuente unica para el JS).

Mismos valores que ``readmenator/_config.py`` BUNDLEGRAPH_*; cada uno
se puede afinar por entorno sin romper el import (env malformada →
default, nunca raise). Los colores viajan por nodo/grupo (campos
``c``), asi que no hay mapa separado.*
- `build_bundle_payload` (line 359) `def build_bundle_payload(force_payload)` - *Deriva el payload bundle desde un payload force RAW.

Las comunidades conservan el orden del analizador (id asc) y los nodos
sin comunidad forman el grupo trailing ``unassigned``. Dentro de cada
grupo los nodos van por ``rank`` desc (hubs al centro del arco/cap).
Solo viajan aristas entidad→entidad de relacion (el scaffolding
``member_of``/``layered_as`` se queda fuera: la jerarquia ya lo pinta).*

#### `graph_force.py`
**Path:** `estorides_core/graph_force.py`
**File Doc:** *graph_force3d: payload force-graph estilo ReadMenator + contexto IA.  Porta ``readmenator/_forcegraph.py`` (payload heterogeneo + SETTINGS) y el formato RAW de ``graph-force.html`` al grafo OSINT de Estorides, mas un constructor de contexto markdown con presupuesto de tokens para la capa de IA local (GraphRAG extractivo, sin llamadas a LLM).  Puro y sin I/O: todo input hostil (labels remotos) se valida fail-closed (``TypeError`` ante valores no JSON-safe) o se trunca a limites fijos. El modulo nunca genera HTML: solo dicts JSON-safe.*

**Functions:**
- `family_color_from_name` (line 48) `def family_color_from_name(name, sat_base, sat_span, light_base, light_span)` - *Deriva un color HSL estable desde un label (djb2, como ReadMenator).*
- `node_value` (line 65) `def node_value(symbols, degree, findings)` - *Escala log2 del tamano de nodo (minimo 1).*
- `force_settings` (line 70) `def force_settings()` - *Valores SETTINGS de ReadMenator graph-force.html (fuente unica).*
- `_req_str` (line 97) `def _req_str(item, key, default, what)` - *Lee un campo string obligatorio; fail-closed ante no-str.*
- `_opt_str` (line 107) `def _opt_str(item, key, default)` - *Lee un campo de estilo; ante no-str usa el default (no mata el grafo).*
- `_opt_int` (line 113) `def _opt_int(item, key, default)` - *Lee un campo entero; ante basura usa el default.*
- `_degrees` (line 124) `def _degrees(node_ids, edges)` - *Grado por nodo + edges validos (ambos extremos conocidos).*
- `_is_bridge` (line 150) `def _is_bridge(edge)` - *Un edge es puente si lo declara o si une clusters distintos.*
- `build_force_payload` (line 161) `def build_force_payload(nodes, edges, clusters, max_nodes, max_edges)` - *Convierte nodos/edges OSINT (`/api/graph`) al formato RAW force-graph.

Truncacion determinista: grado desc, puentes inter-cluster primero,
desempate por `id`. Los edges huerfanos se descartan (conteo en meta).*
- `_md_safe` (line 332) `def _md_safe(text, limit)` - *Neutraliza un string remoto para embeberlo en markdown extractivo.*
- `_truncate_lines` (line 341) `def _truncate_lines(markdown, budget)` - *Corte duro por linea completa + marcador (fences nunca se emiten).*
- `build_ai_context` (line 353) `def build_ai_context(nodes, edges, clusters, budget_chars)` - *Contexto markdown extractivo con presupuesto para la IA local.

Sin llamadas a LLM: entidades top por grado, puentes inter-cluster
primero, una linea por comunidad. Determinista y acotado.*

#### `graph_kuzu.py`
**Path:** `estorides_core/graph_kuzu.py`
**File Doc:** *estorides_core.graph_kuzu ========================= Persistent graph backend powered by Kuzu (embedded Cypher DB).  Why Kuzu and not just another NetworkX dump: NetworkX on disk is read whole on every request. Kuzu gives us indexed Cypher queries so we can ask "every entity connected to <id> within 2 hops" without reloading the world. We still keep NetworkX as the in-memory working graph (the orchestrator depends on it for per-run traversal) — this module mirrors every write to a Kuzu database on disk so cross-run intelligence queries become fast.  Schema mirrors the in-memory graph's node kinds:  Entity nodes: Domain, IPv4, IPv6, Email, URL, Person, Org, ASN, CVE, GHSA, BTCAddress, ETHAddress, Phone, Hash (md5/sha1/sha256/bitcoin_tx), MAC, UserAgent, Country, Sanction Source nodes: one node per OSINT source  Edges: OBSERVED_BY   (Entity -> Source)   "this source produced this entity" CO_OCCURS     (Entity -> Entity)   "appeared in the same response" RESOLVED_TO   (Domain -> IPv4)     "DNS resolution" REGISTERED_BY (Domain -> Org)      "WHOIS registrant" HAS_CVE       (IP -> CVE)          "vulnerability on this host" LOCATED_IN    (Entity -> Country)  "geolocated entity" SANCTIONED    (Entity -> Sanction) "OFAC SDN match" SAME_AS       (Entity -> Entity)   "fuzzy cluster"*

**Classes:**
- `KuzuGraphBackend` (line 196) `class KuzuGraphBackend` - *Thread-safe Kuzu wrapper for the Estorides knowledge graph.

The orchestrator calls this alongside the in-memory NetworkX graph.
Writes are synchronous but cheap; reads (Cypher) are also synchronous
and roughly match NetworkX traversal cost for 2-hop queries while
being dramatically faster at cross-run joins.*

**Functions:**
- `_label_for` (line 124) `def _label_for(ent_type)`
- `_node_id` (line 133) `def _node_id(type_, value)` - *Canonical id used as PRIMARY KEY in Kuzu.

Mirrors the in-memory convention in KnowledgeGraph._node_id so a
`Domain:evilcorp.com` row means the same thing in both stores.*

**Methods:**
- `__init__` (line 205) `def __init__(self, path)`
- `_init_schema` (line 233) `def _init_schema(self)`
- `upsert_entity` (line 245) `def upsert_entity(self, ent_type, value, source)` - *Insert (or merge) an entity. Returns its canonical node id.

If `source` is given we also wire an OBSERVED_BY edge so we
can later ask "which sources saw this entity?".*
- `upsert_relationship` (line 295) `def upsert_relationship(self, src_type, src_value, rel, dst_type, dst_value)` - *Insert an edge between two entities.

Unknown relations are silently skipped — every edge type
that matters is mapped in _RELATION_TO_EDGE.*
- `neighbors` (line 346) `def neighbors(self, node_id, hops, relation, limit)` - *Return nodes reachable from `node_id` within `hops` edges.

`relation` optionally filters to a single edge label.
Returns a list of dicts with whatever columns the query names.*
- `cypher` (line 379) `def cypher(self, query, params)` - *Run a Cypher query and return rows as a list of dicts.

Column names are taken from the RETURN clause. Missing
columns (e.g. `m.kind` on a node that was never enriched) come
back as None so callers don't have to special-case.*
- `stats` (line 406) `def stats(self)` - *Return counts of every node label and edge rel type.*
- `close` (line 436) `def close(self)`

#### `graph_rag_search.py`
**Path:** `estorides_core/graph_rag_search.py`
**File Doc:** *graph_rag_search: GraphRAG local/global para la IA local (sin LLM).  Porta ``readmenator/_graphrag.py`` + ``_rank.py`` al grafo OSINT: indice tipado (entidades, relaciones, text units, reportes de comunidad), BM25 lexico, PageRank global, Personalized PageRank (busqueda local) y map-reduce sobre reportes (busqueda global). Todo extractivo, determinista, puro y sin I/O.*

**Classes:**
- `GraphRagConfig` (line 53) `class GraphRagConfig` - *Presupuestos y pesos de retrieval (env `ESTORIDES_GRAPHRAG_*`).*
- `Bm25Index` (line 112) `class Bm25Index` - *Okapi BM25 sobre un corpus fijo de listas de tokens.*
- `RagEntity` (line 260) `class RagEntity`
- `RagRelation` (line 271) `class RagRelation`
- `RagTextUnit` (line 281) `class RagTextUnit`
- `RagCommunity` (line 289) `class RagCommunity`
- `RagContext` (line 305) `class RagContext`
- `GraphRagIndex` (line 317) `class GraphRagIndex`
- `GraphRagSearcher` (line 593) `class GraphRagSearcher` - *BM25 + PPR (local) y map-reduce sobre reportes (global).*

**Methods:**
- `graph_rag_config_from_env` (line 75) `def graph_rag_config_from_env()` - *Construye la config desde env; malformada → defaults (nunca raise).*
- `tokenize` (line 98) `def tokenize(text, min_len, stopwords)` - *Parte en tokens minusculos; camelCase se divide y se conserva entero.*
- `_stochastic` (line 153) `def _stochastic(ids, edges)` - *Filas estocasticas (indice, prob) + nodos dangling, orden determinista.*
- `pagerank` (line 176) `def pagerank(ids, edges, alpha, max_iter, tolerance)` - *PageRank dirigido y pesado; los scores suman 1 (determinista).*
- `personalized_pagerank` (line 207) `def personalized_pagerank(ids, edges, seeds, alpha, max_iter, tolerance)` - *PPR con teletransporte al seed; seeds ajenos se ignoran.*
- `_md_safe` (line 250) `def _md_safe(text, limit)` - *Gemelo de graph_force._md_safe: neutraliza markdown hostil.*
- `_req_str` (line 354) `def _req_str(item, key, default, what)`
- `_opt_str` (line 363) `def _opt_str(item, key, default)`
- `_opt_int` (line 368) `def _opt_int(item, key, default)`
- `_rel_weight` (line 378) `def _rel_weight(relation)`
- `build_index` (line 382) `def build_index(nodes, edges, clusters, config)` - *Indice extractivo desde nodos/edges estilo `/api/graph` (puro).*
- `_reports` (line 491) `def _reports(nodes, degree, rank, top_mass, weighted, cluster_label, cfg)` - *Un reporte extractivo por cluster + raiz (determinista).*
- `ask_context` (line 857) `def ask_context(query, nodes, edges, clusters, budget_tokens, mode, config)` - *One-shot: build + search (el camino que usa la capa IA local).*
- `_scalar_detail` (line 872) `def _scalar_detail(attrs)` - *Detalle extractivo desde atributos escalares (no escalares se ignoran).*
- `graph_context_block` (line 886) `def graph_context_block(query, nx_graph, cluster_of, budget_tokens, mode, config)` - *Bloque markdown para el prompt LLM desde un grafo networkx (duck-typing).

Grafo vacío → `""` (el caller deja el prompt intacto).*
- `__init__` (line 115) `def __init__(self, documents, k1, b)`
- `scores` (line 136) `def scores(self, query)` - *Un score BM25 por documento, en orden de corpus.*
- `to_dict` (line 324) `def to_dict(self)`
- `from_dict` (line 334) `def from_dict(cls, data)`
- `where` (line 446) `def where(cid)`
- `__init__` (line 596) `def __init__(self, config, index)`
- `_tok` (line 621) `def _tok(self, text)`
- `_entity_doc` (line 624) `def _entity_doc(self, entity)`
- `_symmetric_edges` (line 628) `def _symmetric_edges(self)`
- `choose_mode` (line 635) `def choose_mode(self, query)`
- `search` (line 644) `def search(self, query, mode, budget_tokens)`
- `_budget_chars` (line 652) `def _budget_chars(self, budget_tokens)`
- `local_search` (line 658) `def local_search(self, query, budget_tokens)`
- `global_search` (line 754) `def global_search(self, query, budget_tokens)`
- `_report_block` (line 794) `def _report_block(report)`
- `_unit_block` (line 801) `def _unit_block(unit)`
- `_pack` (line 804) `def _pack(self, header, sections, budget_tokens, shares)`
- `_pack_with` (line 823) `def _pack_with(self, header, sections, shares, budget)` - *Empaqueta por shares; devuelve (texto, hubo_recorte).*
- `rows` (line 335) `def rows(name)`
- `meta` (line 341) `def meta()`

#### `hypothesis_engine.py`
**Path:** `estorides_core/hypothesis_engine.py`
**File Doc:** *estorides_core.hypothesis_engine ================================ The "data → information" layer.  Consumes the per-run ``observations`` and ``entities`` produced by the orchestrator and emits a deduplicated, scored, auditable list of typed ``Hypothesis`` records. Each hypothesis carries the evidence items that justify it (source, field, value, reliability-weighted weight) and a human-readable ``claim`` and ``reasoning`` derived from templates, not from the LLM.  Pure module: no I/O, no DB writes, no logging, no clock. Same input ⇒ same output, bit-by-bit. Hypothesis ids are content hashes so duplicate runs produce the same ids and the fusion store can deduplicate across runs.  Public surface::  generate_hypotheses(observations, entities, kg=None, min_score=0.10, max_hypotheses=50) -> list[Hypothesis]*

**Classes:**
- `EntityRef` (line 57) `class EntityRef` - *A typed reference to an entity involved in a hypothesis.*
- `Evidence` (line 65) `class Evidence` - *One piece of supporting or contradicting evidence.*
- `Hypothesis` (line 76) `class Hypothesis` - *A typed, scored, auditable intelligence conclusion.*
- `HypothesisGenerator` (line 210) `class HypothesisGenerator(Protocol)` - *Strategy: turn a (observations, entities) snapshot into hypotheses.*

**Methods:**
- `_truncate` (line 113) `def _truncate(value)` - *Stringify a value, bounded to ``_VALUE_MAX_CHARS``.*
- `_is_mapping` (line 123) `def _is_mapping(value)`
- `_entity_lookup` (line 127) `def _entity_lookup(entities)` - *Build ``{type: {value, value, ...}}`` from the entity list.

Tolerates both ``Entity`` dataclass instances and plain dicts
(the orchestrator returns both shapes depending on the call site).*
- `_hypothesis_id` (line 148) `def _hypothesis_id(htype, entity_refs, supporting)` - *Deterministic 16-char hex id for a hypothesis.*
- `_score` (line 166) `def _score(supporting, contradicting)` - *Net-support score in (0, 1].

``supporting / (supporting + contradicting + floor)`` — the floor
prevents division by zero and prevents a single weak item from
producing a "1.0 certain" score.*
- `_confidence` (line 180) `def _confidence(supporting, contradicting)` - *Reliability-weighted confidence via :mod:`reliability_scoring`.*
- `_clip_claim` (line 201) `def _clip_claim(template)`
- `_domain_belongsto_actor` (line 220) `def _domain_belongsto_actor(observations, entities)` - *`domain-belongsto-actor`: a domain's WHOIS/issuer/hosting org matches an entity.*
- `_domains_in_obs` (line 312) `def _domains_in_obs(obs)` - *Best-effort: extract domain-like values from a single observation.

The orchestrator doesn't always stamp the queried domain on the
observation, so this is a soft hint. Returns ``[]`` if nothing
looks like a domain.*
- `_email_aliases_person` (line 333) `def _email_aliases_person(observations, entities)` - *`email-aliasto-person`: an email and a person name appear together in one obs.*
- `_extract_email` (line 393) `def _extract_email(parsed)` - *Find a value that looks like an email anywhere in the parsed dict.*
- `_extract_person_name` (line 404) `def _extract_person_name(parsed)` - *Find a value that looks like a person name (has a space, no @, no path).*
- `_ip_shared_infra` (line 415) `def _ip_shared_infra(observations, entities)` - *`ip-shared-infra`: >=2 domains resolve to the same IP.*
- `_extract_ips` (line 496) `def _extract_ips(parsed)` - *Best-effort: pull IPv4-looking values out of a parsed payload.*
- `_looks_like_ipv4` (line 511) `def _looks_like_ipv4(s)`
- `_asn_shared_infra` (line 524) `def _asn_shared_infra(observations, entities)` - *`asn-shared-infra`: >=3 entities of the run live in the same ASN.*
- `_extract_asn` (line 588) `def _extract_asn(parsed)` - *Best-effort: pull an AS-number-ish value out of the parsed payload.*
- `generate_hypotheses` (line 613) `def generate_hypotheses(observations, entities, kg)` - *Generate typed, scored, auditable hypotheses for a run.

Pure: no I/O, no DB writes, no logging, no clock. Same input ⇒
same output bit-by-bit. Hypothesis ids are content hashes so the
fusion store can deduplicate across runs.

Parameters
----------
observations
    Per-source observations from the orchestrator. Each one is
    expected to have at least ``source`` and ``parsed``; the
    generator skips malformed entries without raising.
entities
    The deduplicated entity list from the orchestrator. Each
    item can be a dict (``{"type": ..., "value": ...}``) or an
    ``Entity`` dataclass.
kg
    Optional knowledge graph. Reserved for future generators
    (ego-network, motif-based) — ignored in v1.
min_score
    Floor for the output: hypotheses with score below this are
    dropped. ``[0, 1]``.
max_hypotheses
    Hard cap on the returned list. Top by score.*
- `__call__` (line 213) `def __call__(self, observations, entities)`

#### `ids.py`
**Path:** `estorides_core/ids.py`
**File Doc:** *estorides_core.ids ================== Deterministic content-addressed identifiers.  Six modules (fusion_store, entity_resolution, recon_fusion, change_detection, hypothesis_engine, target_management) each hand-rolled the same `sha1(payload.encode("utf-8"), usedforsecurity=False).hexdigest()[:16]` idiom. SHA-1 here is a content hash for deduplication, **not** a security primitive, hence `usedforsecurity=False` (also silences FIPS-mode concerns).  `stable_id` is the single implementation. Callers own the payload shape (`f"{type}:{normalized}"`, `"|".join([...])`, …) so existing ids are byte-for-byte preserved.*

**Functions:**
- `stable_id` (line 21) `def stable_id(payload, length)` - *Deterministic hex id of `length` chars for a UTF-8 payload.*

#### `intel_resolver.py`
**Path:** `estorides_core/intel_resolver.py`
**File Doc:** *estorides_core.intel_resolver ============================= Cross-feed entity resolver. Inspired by Osiris' `intel/server.js` (simplifaisoul/osiris), the resolver is the single entry point that takes a `(type, id)` pair and returns nodes + edges connecting it across every feed we have a primitive for:  type=ip         -> ASN, ISP, organisation, country (Wikidata + IP-API) type=domain     -> registrant org, ASN, country, sanctions cross-check type=company    -> parent org, country, sanctions (Wikidata + OFAC SDN) type=person     -> employer, nationality, sanctions (Wikidata + OFAC SDN) type=country    -> region, neighbours, risk score type=cve        -> affected vendors, exploits (NVD/EPSS cross-check)  The resolver never *fetches* the source. It composes the local cache (`SanctionsIndex`, `WikidataCache`) with on-demand SPARQL queries when a node is missing. The orchestrator's post-process stage can call this once per significant entity and wire the result into the knowledge graph + Kuzu backend.  Public surface:  resolver = EntityResolver() out = resolver.resolve("ip", "1.2.3.4") out = resolver.resolve("company", "Acme Corp") out = resolver.resolve("person", "Vladimir Putin")  Return shape (matches Osiris' `nodes`/`links` for drop-in reuse):*

**Classes:**
- `_TTLCache` (line 120) `class _TTLCache`
- `EntityResolver` (line 156) `class EntityResolver` - *Cross-feed entity resolution.

Composes Wikidata SPARQL, the OFAC SDN index (via the existing
`OntologyEngine.sanctions`), and a couple of free IP-intel
services into a single response shape suitable for both the UI
(entity graph panel) and the orchestrator (knowledge graph
enrichment).*

**Functions:**
- `_run_sparql` (line 90) `def _run_sparql(query)` - *Execute a SPARQL SELECT against the Wikidata endpoint.

Returns the list of result rows. SSRF-guarded via the URL.*
- `_val` (line 111) `def _val(row, key)` - *Pull a string value out of a SPARQL JSON row.*

**Methods:**
- `_norm` (line 789) `def _norm(s)`
- `_is_valid_ipv4` (line 793) `def _is_valid_ipv4(s)`
- `_escape_sparql` (line 801) `def _escape_sparql(s)`
- `__init__` (line 121) `def __init__(self)`
- `get` (line 127) `def get(self, kind, key)`
- `put` (line 140) `def put(self, kind, key, value)`
- `stats` (line 148) `def stats(self)`
- `__init__` (line 165) `def __init__(self)`
- `resolve` (line 174) `def resolve(self, ent_type, ent_id)`
- `_vt_get` (line 214) `def _vt_get(self, path, limit)` - *GET a VirusTotal v3 path, returning parsed JSON or None.

Reads the API key from `VT_API_KEY`; if it is absent the call
is a silent no-op so the resolver degrades cleanly without a
key. SSRF-guarded via the constructed URL.*
- `_vt_add_relationship` (line 245) `def _vt_add_relationship(self, path)` - *Expand one VirusTotal relationship endpoint into nodes/links.

`attr_field` pulls the related value from `attributes` (e.g.
`host_name` for IP resolutions) instead of the raw object id.*
- `_vt_flag_malicious` (line 290) `def _vt_flag_malicious(self, path, node, sources)` - *Stamp a node with VirusTotal detection stats (counter-intel signal).*
- `_resolve_ip` (line 306) `def _resolve_ip(self, ip)`
- `_resolve_domain` (line 412) `def _resolve_domain(self, domain)`
- `_resolve_file` (line 473) `def _resolve_file(self, file_hash)` - *Resolve a file hash via VirusTotal relationships.

Surfaces the network footprint of a sample (contacted IPs and
domains, dropped/bundled files) and stamps the detection count
so a malicious sample lights up the counter-intelligence tier.*
- `_resolve_company` (line 510) `def _resolve_company(self, name)`
- `_resolve_person` (line 569) `def _resolve_person(self, name)`
- `_resolve_country` (line 638) `def _resolve_country(self, name)`
- `_resolve_cve` (line 678) `def _resolve_cve(self, cve_id)`
- `_resolve_btc` (line 750) `def _resolve_btc(self, addr)`
- `_resolve_eth` (line 753) `def _resolve_eth(self, addr)`
- `_resolve_crypto` (line 756) `def _resolve_crypto(self, addr, kind)`

#### `job_registry.py`
**Path:** `estorides_core/job_registry.py`
**File Doc:** *estorides_core.job_registry ===========================  Bounded, TTL-aware registry for in-memory job objects (RUN_STREAM_JOBS, DISCOVER_JOBS, etc.). The plain `Dict[str, _Job]` pattern that the project shipped with had no eviction at all — see issues #14, #20, #50 — which let an unauthenticated caller grow server memory without bound by repeatedly starting short-lived jobs.  Eviction policy --------------- * **LRU by insertion order** — when the registry hits `max_size`, the oldest job is dropped. Python 3.7+ dicts guarantee insertion order so `next(iter(reg))` is the oldest. * **TTL** — every job has a `created_at`; a sweep on every `register()` drops jobs older than `ttl_seconds`. Cheap when the set is small, and a sweep is unnecessary when the set is empty or the entries are fresh, so the worst-case extra cost is `O(n)` per call, which is dominated by the per-call `register()` itself.  Thread safety ------------- The Flask request handlers that touch these registries run in worker threads (gunicorn forks, multi-worker wsgi). The `register()` / `get()` / `evict_expired()` / `pop()` methods hold a single `threading.Lock` so a sweep and a `get()` cannot race. The cost is the lock acquire on every call, which is negligible compared to the JSON serialisation that follows.*

**Classes:**
- `BoundedJobRegistry` (line 40) `class BoundedJobRegistry` - *A dict-like registry with size and time bounds.

`max_size` and `ttl_seconds` are read at construction and not
mutated — the registry is "configured once, used many times",
which matches how the web app's STREAM config is loaded at
import time.*

**Methods:**
- `__init__` (line 49) `def __init__(self)`
- `register` (line 60) `def register(self, key, value)` - *Insert (or replace) a job, evicting expired and overflow entries.

Returns the value so the caller can use it inline:
    job = registry.register("abc", _RunStreamJob(...))*
- `get` (line 80) `def get(self, key)` - *Return the value for `key` or None. Refreshes the LRU order.*
- `pop` (line 95) `def pop(self, key)` - *Remove and return the value for `key`, or None.*
- `keys` (line 101) `def keys(self)`
- `values` (line 105) `def values(self)`
- `__len__` (line 109) `def __len__(self)`
- `evict_expired` (line 113) `def evict_expired(self)` - *Sweep and drop TTL-expired entries. Returns the number dropped.*
- `_evict_expired_locked` (line 119) `def _evict_expired_locked(self, now)`

#### `knowledge_graph.py`
**Path:** `estorides_core/knowledge_graph.py`
**File Doc:** *estorides_core.knowledge_graph ============================== Persistent in-memory knowledge graph backed by NetworkX.  Nodes = entities (domain, ip, email, person, organisation, cve, etc.) Edges = relationships discovered between them * "observed_by"   entity -> source * "resolved_to"   domain -> ip * "co_occurs"     entity <-> entity   (appeared in the same response) * "mentions"      source -> entity * "located_in"    entity -> place * "registered_by" domain -> org * "has_cve"       ip/port -> cve  GraphML is dumped to disk for analysis in Gephi / Cytoscape / Neo4j.*

**Classes:**
- `KnowledgeGraph` (line 90) `class KnowledgeGraph`

**Functions:**
- `_node_sources` (line 73) `def _node_sources(node)` - *Read the distinct source set off a node, tolerating GraphML's
JSON-string serialisation of the original Python set/list.*

**Methods:**
- `__init__` (line 91) `def __init__(self, name)`
- `add_entity` (line 97) `def add_entity(self, entity)` - *Insert an entity. Returns the node id used.*
- `add_observation` (line 127) `def add_observation(self, source, entities)` - *Add every entity + every co-occurrence edge within the same response.*
- `add_relationship` (line 142) `def add_relationship(self, src_type, src_value, rel, dst_type, dst_value)`
- `export_graphml` (line 164) `def export_graphml(self, path)`
- `export_json` (line 183) `def export_json(self)`
- `export_lazyown` (line 207) `def export_lazyown(self)` - *Emit the graph in LazyOwn intel vocabulary (JSON-serializable).*
- `summary` (line 243) `def summary(self)`
- `top_entities` (line 261) `def top_entities(self, n, by)`
- `communities` (line 281) `def communities(self, nodes)` - *Partition entity nodes into communities (clusters).

Runs greedy modularity on the undirected projection, restricted
to `nodes` when given and always excluding `source` nodes (whose
fan-out would otherwise collapse every cluster into one). Returns
a `node_id -> community index` map. Falls back to connected
components when modularity cannot be computed (e.g. no edges).*
- `intel_level` (line 312) `def intel_level(self, node_id, bridge_nodes)` - *Classify a node into the intelligence pipeline tier.

data                  single corroborating source
information           >= 2 distinct sources
intelligence          cross-cluster bridge or resolved relation
counter_intelligence  sanction/threat/VirusTotal-malicious signal

Higher tiers win. `bridge_nodes` is the set of nodes that sit on
an inter-cluster edge (computed once by the caller).*
- `ego_subgraph` (line 357) `def ego_subgraph(self, node_id, radius)`
- `neighbours` (line 368) `def neighbours(self, node_id, relation)`
- `_node_id` (line 384) `def _node_id(self, kind, value)`
- `_source_node` (line 387) `def _source_node(self, source)`
- `_node_color` (line 397) `def _node_color(self, ent_type)`

#### `lan_bundle.py`
**Path:** `estorides_core/lan_bundle.py`
**File Doc:** *estorides_core.lan_bundle ========================= LAN output deliverables for automation.  Builds inventory, graph, LazyOwn, and STIX payloads from LanDevice lists and writes them atomically to an output directory.*

**Classes:**
- `LanBundleResult` (line 351) `class LanBundleResult` - *Outcome of write_bundle. Failures are values, never raises.*

**Functions:**
- `sanitize_stem` (line 29) `def sanitize_stem(stem)` - *Sanitize a file stem. Bad input falls back to lan.*
- `default_output_dir` (line 40) `def default_output_dir()` - *Resolve the LAN output directory from env or data default.*
- `build_bundle` (line 48) `def build_bundle(devices)` - *Build all four payloads. Never raises on bad items.*
- `_fmt_ts` (line 331) `def _fmt_ts(ts)`
- `_stix_ref` (line 341) `def _stix_ref(node_id, id_map)`

**Methods:**
- `write_bundle` (line 363) `def write_bundle(devices)` - *Write all four deliverables atomically. Never raises.*
- `to_dict` (line 359) `def to_dict(self)`

#### `lan_inventory.py`
**Path:** `estorides_core/lan_inventory.py`
**File Doc:** *estorides_core.lan_inventory ============================ Passive-first LAN device inventory.  Pure parsers for ARP/neighbor tables and DHCP leases plus MAC-keyed device merging. No sockets, no subprocess, no file reads.*

**Classes:**
- `LanDevice` (line 51) `class LanDevice` - *One physical host, merged from one or more sightings.*

**Methods:**
- `normalize_mac` (line 64) `def normalize_mac(raw)` - *Normalize MAC to lowercase colon form, or None when invalid.*
- `_valid_lan_ip` (line 77) `def _valid_lan_ip(raw)`
- `parse_arp_table` (line 91) `def parse_arp_table(text)` - *Parse `ip neigh` or /proc/net/arp text into sightings.*
- `parse_dhcp_leases` (line 119) `def parse_dhcp_leases(text)` - *Parse dnsmasq lease lines into sightings.*
- `parse_nmap_xml` (line 152) `def parse_nmap_xml(text)` - *Parse nmap -oX output into sightings. Fail-soft, size-capped.*
- `parse_nmap_grepable` (line 214) `def parse_nmap_grepable(text)` - *Parse nmap -oG output into sightings. Fail-soft.*
- `parse_active_output` (line 252) `def parse_active_output(text, source)` - *Dispatch active-tool output by format, tagged with source.*
- `merge_devices` (line 269) `def merge_devices(sightings)` - *Merge sightings by MAC, falling back to IP. Never raises.*
- `device_to_entities` (line 331) `def device_to_entities(device)` - *Convert a device into graph-ready entity dicts.*
- `contact_for_tool` (line 352) `def contact_for_tool(tool)` - *Map a tool name to its contact class. Unknown means active.*
- `to_dict` (line 60) `def to_dict(self)`

#### `lan_policy.py`
**Path:** `estorides_core/lan_policy.py`
**File Doc:** *estorides_core.lan_policy ========================= Authorization and contact ceiling for LAN discovery runs.  Single enforcement point for the web active-scan checkbox and the blocking consent gate. Pure: no I/O, no sockets, no subprocess.*

**Classes:**
- `LanPolicyRequest` (line 30) `class LanPolicyRequest` - *Caller intent for a LAN discovery run.*
- `LanPolicyDecision` (line 40) `class LanPolicyDecision` - *Outcome of policy evaluation. JSON serializable via to_dict.*

**Methods:**
- `requires_consent` (line 54) `def requires_consent()` - *Return the canonical consent notice for the blocking modal.*
- `_env_ceiling` (line 59) `def _env_ceiling()`
- `_strict_bool` (line 66) `def _strict_bool(value)`
- `_normalize_scope` (line 70) `def _normalize_scope(raw)` - *Validate scope CIDR. Returns (normalized, error_code).*
- `max_contact_for` (line 97) `def max_contact_for(allow_active, consent)` - *Return active only on the triple gate, else none.*
- `evaluate` (line 104) `def evaluate(request)` - *Evaluate a LAN policy request. Never raises on bad input.*
- `to_dict` (line 50) `def to_dict(self)`

#### `lan_runner.py`
**Path:** `estorides_core/lan_runner.py`
**File Doc:** *estorides_core.lan_runner ========================= Real LAN scan execution: passive readers plus one gated active tool.  Readers are injectable so tests never touch the system. The active path runs only on the lan_policy triple gate through the tool_runner sandbox with a bounded timeout.*

**Classes:**
- `LanScanResult` (line 41) `class LanScanResult` - *Outcome of a LAN scan. Refusals and errors are values.*

**Methods:**
- `read_arp_table` (line 60) `def read_arp_table()` - *Read the local neighbor table. Fail-soft, no probes sent.*
- `read_dhcp_leases` (line 81) `def read_dhcp_leases()` - *Read the first readable dnsmasq lease file. Fail-soft.*
- `_default_runner` (line 93) `def _default_runner()`
- `_attempt` (line 99) `def _attempt(runner, tool, args, scope_cidr, timeout)` - *One sandboxed tool attempt. Stdout on success, None otherwise.*
- `run_active_scan` (line 122) `def run_active_scan(scope_cidr)` - *Run gated active discovery. Returns (tool, stdout).

nmap host discovery first (greppable stdout), arp-scan fallback.
`("", "")` when both fail. Never raises.*
- `scan` (line 147) `def scan(scope_cidr)` - *Execute a LAN scan. Never raises; refusals are values.*
- `to_dict` (line 56) `def to_dict(self)`

#### `lan_scope.py`
**Path:** `estorides_core/lan_scope.py`
**File Doc:** *estorides_core.lan_scope ======================== Private local scope auto-detection.  Pure parsing plus one thin OS reader. The reader inspects local interface configuration only; it sends no probes.*

**Functions:**
- `parse_interface_addr` (line 23) `def parse_interface_addr(raw)` - *Normalize one interface address to CIDR, or None when unsafe.*
- `select_private_cidrs` (line 63) `def select_private_cidrs(interfaces)` - *Keep private CIDRs only, sorted and deduped. Never raises.*
- `default_scope` (line 79) `def default_scope(interfaces)` - *First private CIDR, or None when no private network exists.*
- `parse_ip_addr_output` (line 85) `def parse_ip_addr_output(text)` - *Parse `ip addr` output into address dicts. Pure, never raises.*
- `read_local_interfaces` (line 92) `def read_local_interfaces()` - *Read local interface addresses. Fail-soft, no probes sent.*

#### `lan_web.py`
**Path:** `estorides_core/lan_web.py`
**File Doc:** *estorides_core.lan_web ====================== Backend guard for LAN discovery runs from the web layer.  Thin mapping over lan_policy: the route hands the request body to guard() and returns the verdict status on denial. Pure, no I/O.*

**Classes:**
- `LanWebVerdict` (line 18) `class LanWebVerdict` - *HTTP-ready outcome of the LAN consent gate.*

**Methods:**
- `is_lan_request` (line 50) `def is_lan_request(body, query)` - *True when a run targets the local network. Never raises.*
- `scope_payload` (line 80) `def scope_payload(interfaces)` - *Build the scope response from interface addresses. Never raises.*
- `guard` (line 92) `def guard(body)` - *Validate a run request body for LAN consent. Never raises.*
- `to_dict` (line 29) `def to_dict(self)`
- `to_response` (line 32) `def to_response(self)` - *Return (body, status) for the Flask route.*

#### `mitre_attack.py`
**Path:** `estorides_core/mitre_attack.py`
**File Doc:** *estorides_core.mitre_attack =========================== Lightweight MITRE ATT&CK mapper.  A small but curated lookup table that maps OSINT observation categories to ATT&CK technique IDs. Used to stamp every knowledge- graph observation with the techniques it might support, so the LLM analyst stage can produce a more useful brief ("observations from sources X, Y, Z suggest technique T1595 — Active Scanning").  The full ATT&CK STIX bundle is ~25 MB; we deliberately do NOT download it. The lookup table here covers the ~20 techniques that realistically surface in an OSINT-only context (the rest require active exploitation evidence we don't collect).  This module is a soft enhancement: missing entries are not errors, they just produce no mapping. The orchestrator can call `map_observation(observation)` after the inference step and attach the result to the observation dict.*

**Functions:**
- `_scan_keywords` (line 156) `def _scan_keywords(text)` - *Scan a text blob for ATT&CK-relevant keywords.*
- `map_observation` (line 170) `def map_observation(observation)` - *Return ATT&CK techniques associated with an observation.

Output:
    {
      "techniques": [
        {"id": "T1595", "label": "Active Scanning", "via": "source:shodan_internetdb"},
        ...
      ],
      "tactic_ids": ["TA0043"],  # not populated yet — future
    }*
- `map_observations` (line 213) `def map_observations(observations)` - *Bulk mapper. Stamps each observation in place with `_mitre` key.

Returns the list of observations (for chaining). Mutates in place
for performance — the orchestrator doesn't keep references to the
pre-mapping list, so a side effect is safe.*
- `all_techniques_for` (line 229) `def all_techniques_for(observations)` - *Aggregate: unique techniques across all observations, sorted by id.*

#### `monitoring.py`
**Path:** `estorides_core/monitoring.py`
**File Doc:** *estorides_core.monitoring ========================= Watch targets and scheduling for recurring OSINT monitoring.  Provides: * WatchTarget — a recurring investigation (query + interval + alert channels) * WatchStore — SQLite-backed persistence for watch targets * WatchScheduler — manages the scheduling loop (APScheduler or simple threading) * SchedulerConfig — env-var-tunable configuration  Two modes: 1. In-process threaded scheduler (default) — simple, no external deps. 2. APScheduler — for production deployments with persistent job store.*

**Classes:**
- `WatchTarget` (line 91) `class WatchTarget` - *A recurring OSINT investigation.*
- `WatchStore` (line 155) `class WatchStore(SqliteStore)` - *Thread-safe SQLite store for watch targets.*
- `WatchScheduler` (line 280) `class WatchScheduler` - *Background thread that polls the watch store and triggers due watches.

Uses a simple poll loop (no external deps). For production, swap to
APScheduler by setting ESTORIDES_SCHEDULER_DRIVER=apscheduler.*

**Methods:**
- `__post_init__` (line 106) `def __post_init__(self)`
- `to_dict` (line 110) `def to_dict(self)`
- `from_dict` (line 126) `def from_dict(cls, d)`
- `from_row` (line 142) `def from_row(cls, row)`
- `create_watch` (line 162) `def create_watch(self, watch)` - *Persist a new watch target.*
- `get_watch` (line 177) `def get_watch(self, watch_id)`
- `update_watch` (line 186) `def update_watch(self, watch)`
- `delete_watch` (line 199) `def delete_watch(self, watch_id)`
- `list_watches` (line 203) `def list_watches(self, enabled_only)`
- `due_watches` (line 213) `def due_watches(self, now)` - *Return enabled watches whose next_run_at <= now.*
- `record_run_start` (line 226) `def record_run_start(self, watch_id)` - *Record a watch run start, return history entry id.*
- `record_run_complete` (line 236) `def record_run_complete(self, history_id, status, entity_count, obs_count, error, alert_sent)`
- `history` (line 249) `def history(self, watch_id, limit)`
- `stats` (line 266) `def stats(self)`
- `__init__` (line 287) `def __init__(self, store, runner, alerter)`
- `running` (line 300) `def running(self)`
- `has_runner` (line 304) `def has_runner(self)` - *True once an executor has been wired via set_runner().*
- `start` (line 308) `def start(self)`
- `stop` (line 319) `def stop(self)`
- `set_runner` (line 325) `def set_runner(self, runner)` - *Set the orchestrator runner function (sync or async).*
- `set_alerter` (line 329) `def set_alerter(self, alerter)` - *Set the alert dispatcher.*
- `_loop` (line 333) `def _loop(self)`
- `_execute_watch` (line 344) `def _execute_watch(self, watch)`

#### `observation_models.py`
**Path:** `estorides_core/observation_models.py`
**File Doc:** *estorides_core.observation_models ================================ Strict, versioned Pydantic v2 data contracts for the observation and entity records that flow through the engine.  The engine historically modelled these as unvalidated ``Dict[str, Any]`` built inline in the orchestrator, so a missing field, a wrong-typed value, or a hostile payload could be silently propagated into the fusion store, the knowledge graph, or the LLM analyst. These models are the schema-contract boundary: parsing a dict validates it up front, ``extra="forbid"`` makes schema drift a loud, traceable failure, and ``to_legacy_dict()`` projects back to the exact shape the current in-process call sites consume.  The module is pure (no I/O, no network, no clock reads, no logging of field contents) and runs in the single-process architecture mandated by ``CLAUDE.md``: no Redis, no Celery, no FAISS, no OTLP collector.*

**Classes:**
- `_StrictModel` (line 66) `class _StrictModel(BaseModel)` - *Base class: strict types, forbid unknown fields, no silent coercion.*
- `ObservationMeta` (line 72) `class ObservationMeta(_StrictModel)` - *The ``meta`` sub-object of an observation (status, attempts, cache).*
- `Observation` (line 109) `class Observation(_StrictModel)` - *A validated per-source observation record emitted by the engine.*
- `ObservedEntity` (line 147) `class ObservedEntity(_StrictModel)` - *A validated view of a resolved entity used across the stores.*
- `RunResult` (line 175) `class RunResult(_StrictModel)` - *The top-level validated run payload.*

**Functions:**
- `_check_json_safe` (line 44) `def _check_json_safe(value)` - *Return ``value`` if it is JSON-safe (None/bool/int/float/str/list/dict).

Raises ``ValueError`` on ``set``, ``bytes``, and arbitrary objects so a
hostile or structurally-invalid payload can never be retained in a
validated record. Recurses into containers; dict keys must be strings.*

**Methods:**
- `_bound_url` (line 87) `def _bound_url(cls, value)`
- `_upper_method` (line 92) `def _upper_method(cls, value)`
- `to_legacy_dict` (line 95) `def to_legacy_dict(self)`
- `_json_safe` (line 125) `def _json_safe(cls, value)`
- `to_legacy_dict` (line 128) `def to_legacy_dict(self)`
- `_json_safe` (line 160) `def _json_safe(cls, value)`
- `to_legacy_dict` (line 163) `def to_legacy_dict(self)`
- `to_legacy_dict` (line 183) `def to_legacy_dict(self)`

#### `ontology.py`
**Path:** `estorides_core/ontology.py`
**File Doc:** *estorides_core.ontology ======================= The "centralized ontology engine" — the brain of the platform.  Three things live here:  SanctionsIndex OFAC SDN list, mirrored from OpenSanctions (https://www.opensanctions.org/datasets/latest/us_ofac_sdn/targets.simple.csv). Loaded on first use, refreshed every 24h, single-flight so concurrent requests share one fetch. ~7 MB, low-tens-of-thousands of entries. In-memory graph: {entry} by id + by normalised name + by alias + by crypto address.  WikidataCache A bounded LRU over the Wikidata SPARQL endpoint. Used to canonicalise "Vladimir Putin" → Q7747, "FSB" → Q2084917, etc. 24h TTL per entry, capped at 10k items to bound memory.  OntologyEngine The orchestrator-facing facade. Exposes: - resolve(name) -> candidate sanction matches - resolve_crypto_address(addr) -> sanction cross-check - check_observation(obs) -> {hits, hits_by_field} Designed to be called from a new "post-process" stage that decorates each observation with a sanctions verdict before the LLM analyst sees it.  This module is deliberately dependency-light (only stdlib). The CSV*

**Classes:**
- `SanctionEntry` (line 70) `class SanctionEntry`
- `SanctionsIndex` (line 96) `class SanctionsIndex` - *In-memory OFAC SDN index with 24h lazy refresh and single-flight load.

The previous design (if any) likely fetched the list on every call
or had no cache; the new design:

  * First call to `entries()` blocks while the CSV is fetched.
  * Subsequent calls within TTL return the cached snapshot.
  * Concurrent calls during a load share the same in-flight
    promise (single-flight) — never two concurrent fetches.
  * If a refresh fails after the cache has gone stale, we keep
    serving the previous snapshot rather than going blind.

The CSV is downloaded over HTTPS to a temp file, then loaded
into a normalised lookup. We never trust the file path in
the SDN_LOCAL_CACHE to be writable: on permission errors we
log a warning and fall back to in-memory only.*
- `WikidataCache` (line 268) `class WikidataCache` - *Bounded LRU cache for Wikidata SPARQL queries.

Keyed by `(query_kind, normalised_value)`. Values are `(fetched_at, payload)`.
Exposes `lookup_label(label)` and `lookup_org(label)` — the two
most common lookups in OSINT workflows.*
- `OntologyEngine` (line 314) `class OntologyEngine` - *Public façade. Hands out the sanction index and wikidata cache.*

**Methods:**
- `_normalise_name` (line 84) `def _normalise_name(s)` - *Lower-case, strip punctuation/diacritics, collapse whitespace.

Used for both index keys and incoming query normalisation so the
same string yields the same key on both sides.*
- `to_dict` (line 79) `def to_dict(self)`
- `__init__` (line 115) `def __init__(self)`
- `is_ready` (line 131) `def is_ready(self)`
- `entries` (line 134) `def entries(self)` - *Return the current snapshot, loading if necessary.*
- `lookup` (line 141) `def lookup(self, name)` - *Find sanction entries whose name or alias matches `name`.*
- `lookup_crypto` (line 151) `def lookup_crypto(self, address)` - *Cross-check a BTC/ETH address against the SDN list.

The OpenSanctions CSV includes a `crypto_address` (BTC) field
on a subset of entries. ETH addresses are stored in the
`ethereum_address` field. The current implementation matches
by alias string for simplicity — the upstream coverage of
crypto is sparse and not the focus of this version. Returns
the entries whose alias list contains the literal address.*
- `size` (line 168) `def size(self)`
- `_refresh` (line 172) `def _refresh(self)`
- `_download` (line 201) `def _download(self)`
- `_persist` (line 213) `def _persist(self, text)` - *Best-effort write of the raw CSV for offline re-use.

Failures are non-fatal; the in-memory index is the source of
truth while the process is alive.*
- `_parse` (line 226) `def _parse(self, text)` - *Parse the OpenSanctions simple CSV into SanctionEntry records.*
- `_index` (line 255) `def _index(self, entries)` - *Build a normalised-name → entries lookup.*
- `__init__` (line 276) `def __init__(self)`
- `get` (line 282) `def get(self, kind, value)`
- `put` (line 296) `def put(self, kind, value, payload)`
- `stats` (line 304) `def stats(self)`
- `clear` (line 308) `def clear(self)`
- `__init__` (line 317) `def __init__(self)`
- `check_observation` (line 321) `def check_observation(self, observation)` - *Run a single observation through the ontology.

Returns a dict describing the sanctions verdict:

  {
    "sanctioned": bool,
    "hits": [SanctionEntry.to_dict(), ...],
    "fields": ["registrant_name", "isp", ...]  # which fields matched
  }

The orchestrator attaches this to each observation before the
LLM analyst stage so the system prompt can include a verdict
line ("SANCTIONED — OFAC SDN match on registrant").*
- `_candidate_fields` (line 359) `def _candidate_fields(source, parsed)` - *Return (field_name, value) pairs to check against sanctions.

Different sources expose different field names. Rather than
a giant if/elif, this is a small dispatch table — adding a
new source is a one-line edit.*

#### `openapi.py`
**Path:** `estorides_core/openapi.py`
**File Doc:** *estorides_core.openapi ====================== Minimal OpenAPI document generated from the live Flask url map.  I expose paths, methods and auth requirements without new dependencies and without duplicating the route table by hand.*

**Functions:**
- `build_openapi` (line 14) `def build_openapi(app)` - *Build an OpenAPI 3.0 document from Flask routes.*

#### `ops_observability.py`
**Path:** `estorides_core/ops_observability.py`
**File Doc:** *estorides_core.ops_observability ================================ Single user operational visibility without external dependencies.  I provide unauthenticated liveness, authenticated readiness and Prometheus text metrics plus an opt in JSON log formatter. I keep all counters in process behind a lock so threaded Flask workers cannot corrupt them. I sanitize every label so a hostile source name cannot inject newlines into the scrape output.*

**Classes:**
- `OpsConfig` (line 28) `class OpsConfig` - *Centralized bounds for observability output.*

**Methods:**
- `_clean_label` (line 43) `def _clean_label(value)` - *Return a Prometheus safe label fragment without newlines.*
- `health_payload` (line 50) `def health_payload()` - *Return liveness body and status code.*
- `ready_payload` (line 55) `def ready_payload(source_count, sources_dir_ok)` - *Return readiness body and status code for registry state.*
- `record_request` (line 62) `def record_request(endpoint, status)` - *Count one HTTP response by normalized endpoint and status.*
- `record_source` (line 72) `def record_source(source, ok)` - *Count one source execution failure when ok is False.*
- `reset_metrics` (line 83) `def reset_metrics()` - *Clear all in memory counters for tests and process restart.*
- `render_metrics` (line 90) `def render_metrics()` - *Render counters in Prometheus exposition text format.*
- `format_event` (line 112) `def format_event(fields, as_json)` - *Format one log event as JSON when opted in else legacy plain text.*
- `project_root` (line 130) `def project_root()` - *Expose project root without hardcoding absolute paths in callers.*

#### `orchestrator.py`
**Path:** `estorides_core/orchestrator.py`
**File Doc:** *estorides_core.orchestrator ============================ The main entry point. Glues together: * source registry  (source_loader) * async http       (async_client) * parsers          (parsers) * entity extract   (entity_extraction) * knowledge graph  (knowledge_graph) * LLM manager      (llm.manager) * dataset export   (exporters)  Public surface: orchestrator = Orchestrator() await orchestrator.run(query, ...)*

**Classes:**
- `Orchestrator` (line 168) `class Orchestrator`

**Functions:**
- `pending_system_app_tasks` (line 56) `def pending_system_app_tasks()` - *Number of slow CLI-tool runs still in flight (for the stream gate).*
- `_safe_format` (line 115) `def _safe_format(template)` - *Format a string template with {key} placeholders.

Non-string values (int, list, dict) are returned as-is so YAMLs that
use raw integers or list literals in params/body don't blow up.*
- `_resolve_auth` (line 127) `def _resolve_auth(source)` - *Look up the API key for a source that needs one.*
- `_expand_query_type` (line 152) `def _expand_query_type(query_type)` - *Return the set of applies_to tags a query_type matches.*
- `_domain_from_query` (line 158) `def _domain_from_query(q)` - *Heuristic: if the query looks like a domain, return it; if it's an IP, return None.*

**Methods:**
- `repl` (line 122) `def repl(m)`
- `__init__` (line 169) `def __init__(self, registry, llm, kg)`
- `run` (line 190) `def run(self, query)` - *Run a full intelligence cycle. Returns a structured result.

`deadline` is a hard wall-clock cap (seconds) for the whole fanout.
Any source that hasn't responded by then is dropped and reported as
"deadline_exceeded" so the run can never get stuck.

`persist` (default True) writes the run to the persistent case
store and mirrors every entity/edge to the Kùzu graph. Set
False for one-off ad-hoc queries (e.g. tests) where you don't
want the run to bloat the long-term memory.

`case_id`, when supplied, makes this run append to an existing
case instead of opening a new one. The recursive pivot engine
uses this so every hop of a cross-search lands in a single case.

`on_source_result`, when supplied, is invoked with the shaped
observation dict the moment each source resolves, before the
fan-out as a whole completes. This is what lets the UI populate
progressively instead of blocking on the slowest source.

`passive_only` (default False) restricts the fan-out to sources
whose contact class is `none` — the target's own infrastructure is
never touched, not even by a third-party broker probe. Use this for
bug-bounty scoping where a probe attributable to the operator's
recon window must not appear in the target's logs.

`proxy`, when supplied (e.g. `socks5://127.0.0.1:9050` for Tor),
routes every outbound request through it so the queried brokers
never see the operator's real egress IP. Falls back to the
env-configured proxy pool when omitted.*
- `_select_sources` (line 671) `def _select_sources(self, names)`
- `_execute_source` (line 707) `def _execute_source(self, client, source, query, on_done, on_result)` - *Dispatch a source to its execution strategy.*
- `_normalise_results` (line 724) `def _normalise_results(self, raw_results, targets)` - *Sweep gather output into uniform 4-tuples.

Each successful task already returns its own originating source
as element zero, so alignment never depends on positional zip
against the target list. The zip fallback only supplies a source
name for exceptions and bad shapes.*
- `_source_of` (line 764) `def _source_of(item, fallback, raw)` - *Best-effort source for a broken result: positional fallback.*
- `_run_system_app` (line 774) `def _run_system_app(self, source, query, on_done, on_result)` - *Run a `system_app` source through the tool_runner sandbox.*
- `_run_http_source` (line 841) `def _run_http_source(self, client, source, query, on_done, on_result)`
- `_extract_cursor` (line 953) `def _extract_cursor(data, cfg)` - *Extract the next-page cursor from a parsed response body.*
- `_infer_relationships` (line 958) `def _infer_relationships(self, observations, query)` - *Delegate each observation to its registered inferer.

The previous version of this method was a 90-line `if/elif`
chain hard-coded to specific source names. The new version
walks the inferer registry; sources with no inferer are
silently skipped. Adding a new inferer is now: write a
function and `@register_inferer("source_name")` it. No edits
to this method.*
- `_write_dataset` (line 971) `def _write_dataset(self, query, observations, entities, analysis)`

#### `osiris_sources.py`
**Path:** `estorides_core/osiris_sources.py`
**File Doc:** *estorides_core.osiris_sources ============================= Extra OSINT endpoints borrowed from the Osiris project (simplifaisoul/osiris). Each function returns a Flask Response-ready dict; the Blueprint in `estorides_web` wires them up to public routes.  Why a separate module: The main 99 sources are YAML-defined and go through the orchestrator pipeline (parser → entity extraction → KG). The Osiris-style endpoints are narrower: they hit a single free API and return a flat JSON response without going through the orchestrator. Mixing them into the YAML registry would force a parallel "raw passthrough" parser and bloat `parsers.py`. Keeping them here makes the boundary explicit and easy to audit.  Source map (Osiris → here): /api/osiris/bgp        bgpview.io (keyless) /api/osiris/mac        macvendors.co (keyless) /api/osiris/phone      local libphonenumber + region centroid table /api/osiris/github     api.github.com (keyless, rate-limited) /api/osiris/leaks      api.xposedornot.com (keyless, more detail than HIBP) /api/osiris/cisa-kev   cisa.gov KEV JSON feed /api/osiris/malware    feodotracker.abuse.ch + urlhaus-api.abuse.ch*

**Functions:**
- `_cached_get` (line 81) `def _cached_get(url)` - *GET with a small on-disk JSON cache. Returns parsed JSON or None.

Used for the slow-changing feeds (CISA KEV, malware lists) so a
burst of operator clicks doesn't hammer the upstream.*
- `fetch_bgp` (line 119) `def fetch_bgp(query)` - *Look up an IP or AS number against bgpview.io (free, no key).*
- `fetch_mac` (line 185) `def fetch_mac(mac)` - *Look up a MAC address against macvendors.co (free, no key).*
- `fetch_phone` (line 233) `def fetch_phone(number)` - *Best-effort phone geolocation.

The implementation is intentionally simple (regex + region
table). It does NOT replace libphonenumber; it provides a
same-shape response without the dep so the API stays consistent
with Osiris' /api/osint/phone.*
- `fetch_github_user` (line 302) `def fetch_github_user(username)` - *Look up a GitHub user (keyless, rate-limited).*
- `fetch_leaks` (line 356) `def fetch_leaks(email)` - *Breach analytics for `email` via xposedornot (free, no key).*
- `fetch_cisa_kev` (line 400) `def fetch_cisa_kev(limit, days)` - *Recently-added CVEs from the CISA KEV feed (authoritative).*
- `fetch_malware_c2` (line 452) `def fetch_malware_c2(limit)` - *Active botnet C2 (Feodo) + recent malware URLs (URLhaus).

Both abuse.ch, both keyless. Each entry is geolocated against the
country centroid table with deterministic jitter so multiple
threats in the same country don't stack on the same pixel.*

#### `pagination.py`
**Path:** `estorides_core/pagination.py`
**File Doc:** *estorides_core.pagination ========================= Pagination strategies for source API fetches.  Pure dataclasses + iteration logic. No I/O, no asyncio, no HTTP. The orchestrator owns the fetch loop; this module only describes *how* to advance from one page to the next and *when* to stop.*

**Classes:**
- `PaginationConfig` (line 19) `class PaginationConfig` - *Configuration for a paginated source fetch.

Parsed from the source YAML's ``pagination`` key. No fields are
required; a source with no pagination config results in a single
fetch (the current default behaviour).*

**Methods:**
- `build_page_params` (line 62) `def build_page_params(cfg, page_num)` - *Build URL params dict for a given page number.

Returns an empty dict for cursor strategy (the cursor is set
dynamically from the response) or when pagination is disabled.*
- `extract_cursor` (line 78) `def extract_cursor(data, cfg)` - *Extract the next-page cursor from a parsed response body.

Walks the dot-separated ``cursor_path`` into the JSON-like dict.
Returns ``None`` when the path is absent or the value is empty.*
- `count_results` (line 99) `def count_results(data, cfg)` - *Count results in a parsed response page.

Uses ``response_list_path`` if configured, otherwise tries common
JSON fields (``results``, ``items``, ``data``) or falls back to
``len(data)`` for a list-type response.*
- `from_dict` (line 38) `def from_dict(raw)`
- `enabled` (line 54) `def enabled(self)`
- `needs_page_size` (line 58) `def needs_page_size(self)`

#### `parsers.py`
**Path:** `estorides_core/parsers.py`
**File Doc:** *estorides_core.parsers ====================== A small library of structured parsers. Each parser knows how to take a raw response from a specific OSINT source and pull out the things the rest of the pipeline cares about: domain, IP, geolocation, etc.  The `parser` field of each YAML source selects one of these functions via the `PARSERS` registry. New parsers are added with `@register_parser("name")` or by appending to `PARSERS` directly.*

**Functions:**
- `_d` (line 31) `def _d(obj)` - *Return `obj` as a dict, or an empty dict when it is anything else.

Remote JSON frequently puts `null`/a string where a nested object is
expected. Coercing here keeps every parser total (the module contract).*
- `_first_dict` (line 40) `def _first_dict(items)` - *Return the first dict element of `items`, else an empty dict.*
- `_list` (line 47) `def _list(obj)` - *Return `obj` as a list, or [] when it is anything else.

Guards the very common `...get("items") or []` idiom, which is NOT
safe against a truthy non-iterable (a remote `"items": true`).*
- `_text` (line 56) `def _text(obj)` - *Return `obj` as a string, or "" when it is anything else.*
- `parse_dns_json` (line 62) `def parse_dns_json(payload)` - *Google/Cloudflare DNS-over-HTTPS response.*
- `parse_crtsh_json` (line 78) `def parse_crtsh_json(payload)` - *crt.sh CT log response.*
- `parse_rdap` (line 96) `def parse_rdap(payload)` - *RDAP (RFC 7483) domain object.

Returns a flat dict with registrar, registry handle, status flags,
event dates, and any nameserver / entity hints. The structured
result feeds two goals:

  1. Surface the registrar as an `entity` so the resolver can
     later ask "which other domains does MarkMonitor manage"
     (or whichever registrar came back) and fan out into the
     shared-infrastructure lane.
  2. Save the create / expire / updated dates so the timeline
     view can render them without a second pass.

Defensive against missing fields — RDAP responses vary across
registries and the spec allows a lot of optional bits.*
- `parse_ipapi` (line 176) `def parse_ipapi(payload)` - *ip-api.com response.*
- `parse_ipinfo` (line 202) `def parse_ipinfo(payload)`
- `parse_ipapi_co` (line 217) `def parse_ipapi_co(payload)`
- `parse_shodan_internetdb` (line 227) `def parse_shodan_internetdb(payload)` - *internetdb.shodan.io — IP service summary.*
- `parse_greynoise` (line 241) `def parse_greynoise(payload)`
- `parse_ipwhois` (line 256) `def parse_ipwhois(payload)`
- `parse_abuseipdb` (line 274) `def parse_abuseipdb(payload)`
- `_vt_stats` (line 290) `def _vt_stats(attrs)` - *Flatten VirusTotal v3 last_analysis_stats into a compact dict.*
- `parse_vt_ip` (line 304) `def parse_vt_ip(payload)` - *VirusTotal v3 — IP address object.*
- `parse_vt_domain` (line 325) `def parse_vt_domain(payload)` - *VirusTotal v3 — domain object.*
- `parse_vt_file` (line 352) `def parse_vt_file(payload)` - *VirusTotal v3 — file object.*
- `parse_bgpview` (line 376) `def parse_bgpview(payload)` - *BGPView IP/ASN response (keyless). Total: never raises.*
- `parse_cisa_kev` (line 408) `def parse_cisa_kev(payload)` - *CISA KEV catalog (keyless). Capped at 20 items. Total: never raises.*
- `parse_ripe_stat` (line 427) `def parse_ripe_stat(payload)`
- `parse_nominatim` (line 438) `def parse_nominatim(payload)`
- `parse_urlscan` (line 456) `def parse_urlscan(payload)`
- `parse_wayback_cdx` (line 478) `def parse_wayback_cdx(payload)` - *CDX returns a list where the first row is the header.*
- `parse_wayback_avail` (line 493) `def parse_wayback_avail(payload)`
- `parse_threatfox` (line 503) `def parse_threatfox(payload)`
- `parse_urlhaus` (line 512) `def parse_urlhaus(payload)`
- `parse_urlhaus_payloads` (line 521) `def parse_urlhaus_payloads(payload)`
- `parse_malwarebazaar` (line 530) `def parse_malwarebazaar(payload)`
- `parse_otx` (line 539) `def parse_otx(payload)`
- `parse_hibp_breach` (line 564) `def parse_hibp_breach(payload)`
- `parse_hibp_paste` (line 582) `def parse_hibp_paste(payload)`
- `parse_phonebook` (line 598) `def parse_phonebook(payload)`
- `parse_wikipedia` (line 619) `def parse_wikipedia(payload)`
- `parse_wikidata` (line 628) `def parse_wikidata(payload)`
- `parse_openalex` (line 640) `def parse_openalex(payload)`
- `parse_crossref` (line 665) `def parse_crossref(payload)`
- `parse_arxiv` (line 685) `def parse_arxiv(payload)` - *arXiv returns Atom XML; we expect callers to have converted to a dict.*
- `parse_nvd_cve` (line 706) `def parse_nvd_cve(payload)`
- `parse_github_advisories` (line 727) `def parse_github_advisories(payload)`
- `parse_blockchain_btc` (line 752) `def parse_blockchain_btc(payload)`
- `parse_blockstream` (line 769) `def parse_blockstream(payload)`
- `parse_ethplorer` (line 785) `def parse_ethplorer(payload)`
- `parse_microlink` (line 802) `def parse_microlink(payload)`
- `parse_github_user` (line 822) `def parse_github_user(payload)`
- `parse_github_search` (line 842) `def parse_github_search(payload)`
- `parse_reddit` (line 857) `def parse_reddit(payload)`
- `parse_mastodon` (line 887) `def parse_mastodon(payload)`
- `parse_keybase` (line 903) `def parse_keybase(payload)`
- `parse_hackernews` (line 932) `def parse_hackernews(payload)`
- `parse_reddit_search` (line 944) `def parse_reddit_search(payload)`
- `parse_dev_to` (line 958) `def parse_dev_to(payload)`
- `parse_text_lines` (line 973) `def parse_text_lines(payload)` - *Generic: split raw_text by newlines, drop empties.*
- `parse_raw_text` (line 984) `def parse_raw_text(payload)`
- `parse_http_headers` (line 992) `def parse_http_headers(payload)` - *hackertarget returns text; expect a one-line-per-header response.*
- `parse_whois_text` (line 1008) `def parse_whois_text(payload)`
- `parse_twitter_user` (line 1026) `def parse_twitter_user(payload)` - *Twitter/X API v2 user by username.

Expects the v2 JSON shape: {"data": {"id": ..., "name": ..., "username": ..., ...}}
Returns flat dict with the core profile fields.*
- `parse_youtube_user` (line 1062) `def parse_youtube_user(payload)` - *YouTube Data API v3 channel by handle.

Expects: {"items": [{"id": ..., "snippet": {...}, "statistics": {...}}]}
Returns a flat profile dict with channel metadata.*
- `parse_twitch_user` (line 1099) `def parse_twitch_user(payload)` - *Twitch Helix API user by login.

Expects: {"data": [{"id": ..., "login": ..., "display_name": ..., ...}]}
Returns a flat profile dict with core Twitch metadata.*
- `parse_discord_discovery` (line 1132) `def parse_discord_discovery(payload)` - *Discord server discovery via discords.com API.

Expected shape: a list of server dicts with name, description,
member_count, etc. Or a dict with a 'results' or 'servers' key.*
- `_normalise_discord_server` (line 1152) `def _normalise_discord_server(raw)` - *Normalise a single raw Discord server dict into a standard shape.*
- `get_parser` (line 1251) `def get_parser(name)` - *Return the parser function for `name`, or a passthrough lambda.

Unknown parser names deliberately fall through to `parse_raw_text`
so a source YAML with a typo in the `parser` field never crashes
the run — it just produces a less-structured observation.*
- `register_parser` (line 1264) `def register_parser(name, description)` - *Decorator: register `func` as a parser under `name`.

Used by addon authors and tests to extend the catalog without
touching the central `PARSERS` dict. Idempotent: re-registering
the same name overwrites the previous entry, with a debug log so
a typo doesn't silently drop a parser.*
- `list_parsers` (line 1280) `def list_parsers()` - *Return (name, description) tuples for every registered parser.

Used by the CLI `status` endpoint to advertise the available
parser names, and by tests to assert that a custom parser made it
into the registry.*
- `deco` (line 1272) `def deco(func)`

#### `pdns_monitor.py`
**Path:** `estorides_core/pdns_monitor.py`

**Classes:**
- `HistoricalSubdomain` (line 13) `class HistoricalSubdomain`
- `IPRecord` (line 27) `class IPRecord`
- `CertRecord` (line 40) `class CertRecord`
- `PDNSResult` (line 55) `class PDNSResult`

**Methods:**
- `classify_subdomain_status` (line 72) `def classify_subdomain_status(fqdn, resolved_ips)`
- `extract_sans_from_cert` (line 76) `def extract_sans_from_cert(cert)`
- `analyse_pdns_data` (line 80) `def analyse_pdns_data(subdomains, ip_history, new_certs)`
- `to_dict` (line 22) `def to_dict(self)`
- `to_dict` (line 35) `def to_dict(self)`
- `to_dict` (line 50) `def to_dict(self)`
- `to_dict` (line 62) `def to_dict(self)`

#### `people_intel.py`
**Path:** `estorides_core/people_intel.py`

**Classes:**
- `BreachRecord` (line 15) `class BreachRecord`
- `Employee` (line 29) `class Employee`
- `BreachContext` (line 56) `class BreachContext`
- `PeopleIntelResult` (line 67) `class PeopleIntelResult`

**Methods:**
- `infer_email_pattern` (line 98) `def infer_email_pattern(emails)`
- `_match_pattern` (line 136) `def _match_pattern(local)`
- `_severity_from_breaches` (line 143) `def _severity_from_breaches(breaches)`
- `correlate_breaches` (line 153) `def correlate_breaches(employees)`
- `analyse_employees` (line 176) `def analyse_employees(employees, domain)`
- `to_dict` (line 22) `def to_dict(self)`
- `to_dict` (line 40) `def to_dict(self)`
- `to_dict` (line 62) `def to_dict(self)`
- `to_dict` (line 75) `def to_dict(self)`

#### `pivot_engine.py`
**Path:** `estorides_core/pivot_engine.py`
**File Doc:** *estorides_core.pivot_engine =========================== Recursive, asynchronous cross-search engine — the Palantir-style core.  Given a seed selector (a domain, IP, email, ASN, wallet), the engine runs the full OSINT fan-out, harvests the selectors that surface, scores them, and re-queries the highest-value ones, hop by hop, until a bounded budget is spent. Every hop appends to a single case so the cross-search reads as one coherent investigation.  The design is deliberately decoupled (SOLID):  * `EntityRunner`  — an abstraction over "run one target, give me the observations and entities". Satisfied by the existing Orchestrator. * `EventSink`     — an abstraction over "publish a progress event". The SSE layer, a test list, or a log adapter all satisfy it. The engine knows nothing about HTTP. * `PivotPolicyConfig` (in config) — decides which selectors pivot and how leads are scored. Swapping the policy swaps the behaviour without touching the engine (open/closed). * `PivotConfig` (in config) — every bound and default. No literal in this module is a tunable number.  The frontier is a max-heap by lead score, so a high-signal selector (an email, a wallet) expands before low-signal shared infrastructure at the same depth. Confidence decays with depth and is carried on every emitted entity so a downstream consumer can rank or prune.*

**Classes:**
- `PivotEvent` (line 47) `class PivotEvent` - *A single, transport-neutral progress event.

`type` is a stable vocabulary token; `data` is a JSON-serialisable
payload. Sinks translate these to their own wire format.*
- `EventSink` (line 59) `class EventSink(Protocol)` - *Receives engine progress events. Implementations must not raise.*
- `ListEventSink` (line 67) `class ListEventSink` - *In-memory sink. Useful for tests and synchronous embedding.*
- `BufferedEventSink` (line 77) `class BufferedEventSink` - *Bounded sink that flattens events to JSON-ready dicts for SSE drain.

Each stored item is `{"type": ..., "ts": ..., **data}`. When the buffer
exceeds `capacity` the oldest items are dropped and a `heartbeat` marker
records how many were lost, so a slow client degrades gracefully instead
of stalling the producer. The terminal `finished`/`fatal` event also
flips `done` so a poller knows when to stop without parsing payloads.*
- `EntityRunner` (line 113) `class EntityRunner(Protocol)` - *Runs the OSINT fan-out for a single target.

The engine depends on this narrow port rather than the concrete
Orchestrator, so it can be driven by a stub in tests.*
- `PivotBudget` (line 139) `class PivotBudget` - *Mutable accounting for one cross-search.

Holds the three hard ceilings (steps, entities, wall-clock) and the
monotonic clock the deadline is measured against. `exhausted()` returns
a human reason string the moment any ceiling is hit, else None.*
- `PivotLead` (line 172) `class PivotLead` - *A target waiting in the frontier.*
- `PivotResult` (line 184) `class PivotResult` - *Terminal summary of a completed cross-search.*
- `PivotEngine` (line 195) `class PivotEngine` - *Drives the recursive, scored, asynchronous cross-search.*

**Methods:**
- `emit` (line 62) `def emit(self, event)` - *Publish one event. A slow or failing sink must never break a run.*
- `__init__` (line 70) `def __init__(self)`
- `emit` (line 73) `def emit(self, event)`
- `__init__` (line 87) `def __init__(self, capacity)`
- `emit` (line 94) `def emit(self, event)`
- `run` (line 120) `def run(self, query)`
- `time_left` (line 155) `def time_left(self)` - *Seconds remaining before the global wall-clock deadline.*
- `exhausted` (line 159) `def exhausted(self)` - *Reason the run must stop, or None while budget remains.*
- `__init__` (line 198) `def __init__(self, runner, sink)`
- `_emit` (line 246) `def _emit(self, event_type)` - *Build and publish an event, swallowing any sink failure.*
- `_heap_push` (line 255) `def _heap_push(heap, counter, lead)` - *Push a lead as a max-heap by score (negated for heapq).*
- `run` (line 264) `def run(self, seed_type, seed_value)` - *Execute the cross-search from `(seed_type, seed_value)`.

Returns a `PivotResult`. Progress is published incrementally
through the injected sink while the coroutine is in flight.*
- `_expand_lead` (line 341) `def _expand_lead(self, lead, frontier, budget)` - *Run the fan-out for one lead and enqueue its scored children.*
- `_ingest_children` (line 411) `def _ingest_children(self, parent, result, frontier, budget)` - *Score the entities a target produced and enqueue the best ones.

Returns the number of leads actually added to the frontier. The
per-step breadth cap keeps a single popular target (a CDN, a
registrar) from flooding the queue.*
- `_on_source_done` (line 356) `def _on_source_done(name, ok, status, elapsed_ms)`
- `_on_source_result` (line 366) `def _on_source_result(observation)`

#### `recon_fusion.py`
**Path:** `estorides_core/recon_fusion.py`
**File Doc:** *estorides_core.recon_fusion =========================== Passive reconnaissance fusion engine: groups, deduplicates and classifies OSINT results by relevance tier so the operator sees the most useful information first.  Pure transformation: List[Observation] + List[Entity] + Query -> FusionResult.*

**Classes:**
- `RelevanceTier` (line 27) `class RelevanceTier(str, Enum)` - *Relevance classification for grouped reconnaissance results.

Ordered from most to least relevant for UI rendering.*
- `GroupedEntity` (line 46) `class GroupedEntity` - *One canonical entity grouped across all sources that observed it.*
- `FusionResult` (line 84) `class FusionResult` - *Complete output of the recon fusion engine.*
- `ReconFusionEngine` (line 160) `class ReconFusionEngine` - *Stateless engine that classifies raw OSINT results into relevance tiers.

Thread-safe (no mutable state). Reusable across requests.*

**Methods:**
- `_normalize_value` (line 107) `def _normalize_value(etype, value)` - *Deterministic normalisation matching fusion_store.entity_id.*
- `_canonical_id` (line 112) `def _canonical_id(etype, value)` - *Deterministic sha1-based entity id matching fusion_store convention.*
- `_corroboration_factor` (line 117) `def _corroboration_factor(source_count)` - *Logarithmic corroboration weight: min(1, log10(1 + n)).*
- `_freshness_factor` (line 124) `def _freshness_factor(age_hours, max_hours)` - *Linear freshness decay from 1.0 (fresh) to 0.1 (stale).*
- `_direct_match_query` (line 132) `def _direct_match_query(value, query)` - *True if the entity value matches the original query.*
- `_extract_key_findings` (line 137) `def _extract_key_findings(observations)` - *Extract textual key findings from a list of observations.*
- `ordered` (line 40) `def ordered(cls)` - *Return tiers in canonical display order.*
- `to_dict` (line 64) `def to_dict(self)`
- `to_dict` (line 95) `def to_dict(self)`
- `__init__` (line 166) `def __init__(self, config)`
- `classify` (line 169) `def classify(self, query, query_type, observations, entities)` - *Classify raw observations and entities into relevance-tiered groups.

Args:
    query: The original operator query (non-empty).
    query_type: Detected type of the query.
    observations: Raw observation dicts from the orchestrator.
    entities: Raw entity dicts from entity extraction.

Returns:
    FusionResult with tiered, grouped, deduplicated results.

Raises:
    ValueError: If query is empty.*
- `_deduplicate` (line 219) `def _deduplicate(self, observations)` - *Remove exact duplicates based on config dedup keys.

Two observations are identical if they share the same values for all
exact_dedup_keys. The first occurrence is kept.*
- `_group_by_entity` (line 237) `def _group_by_entity(self, observations, entities)` - *Group observations and entities by canonical entity id.

Primary grouping key is the explicit entity list. Observations are
attached to groups when the entity value appears in the observation's
parsed data or the observation source matches the entity's sources.*
- `_classify_groups` (line 321) `def _classify_groups(self, groups, query)` - *Assign each group a relevance tier and score.*
- `_assign_tier` (line 386) `def _assign_tier(self, source_count, avg_reliability, direct_match)` - *Determine the relevance tier based on source count and reliability.

Thresholds:
    CRITICAL: source_count >= critical_min_sources (3) OR
              (source_count >= 2 AND avg_reliability >= B weight)
    HIGH:     source_count >= high_min_sources (2) OR
              (source_count >= 1 AND avg_reliability >= high_min_reliability AND direct_match)
    MEDIUM:   source_count >= 1 AND avg_reliability > noise threshold
    LOW:      source_count >= 1 AND avg_reliability <= noise threshold
    NOISE:    source_count >= 1 AND avg_reliability <= F weight
              OR no corroboration from untrusted source*

#### `recon_pipeline.py`
**Path:** `estorides_core/recon_pipeline.py`

**Functions:**
- `run_passive_recon` (line 22) `def run_passive_recon(query, headers, html, cookies, employees, code_findings, third_parties, pdns_subdomains, cloud_assets)`

#### `relationship_inference.py`
**Path:** `estorides_core/relationship_inference.py`
**File Doc:** *estorides_core.relationship_inference ===================================== Strategy registry for deriving higher-level knowledge-graph edges from raw observations.  The orchestrator's old `_infer_relationships()` was a chain of hard-coded `if src == "foo": ...` blocks — every new source that wanted to contribute edges meant editing the central orchestrator. This module replaces that with a registry: a `RelationshipInferer` is a small callable that knows how to translate one observation into graph edges, and the orchestrator walks the registry.  Add a new inferer by: 1. Writing a function `def my_inferer(observation, query, kg) -> None` 2. Decorating it with `@register_inferer("source_name")`  The orchestrator picks the right inferer by the observation's `source` field, so the source name and inferer name must match.  An inferer is a NO-OP if the observation has nothing useful to contribute (returns None), or raises a `ValueError` if the observation is malformed in a way the orchestrator should log and skip.*

**Classes:**
- `RelationshipInferer` (line 36) `class RelationshipInferer(Protocol)` - *Translate a single observation into one or more knowledge-graph edges.

Args:
    observation: the structured observation dict produced by the
        orchestrator. Has keys: source, category, parsed, raw, meta.
    query: the user's original query string. Useful for edges that
        connect a finding back to the pivot (e.g. "domain X
        resolved to IP Y for query X").
    kg: the KnowledgeGraph to mutate. The inferer adds nodes and
        edges via the existing `add_relationship()` API.

Returns:
    None. Side effects only: writes to the knowledge graph.*

**Methods:**
- `register_inferer` (line 63) `def register_inferer(source_name)` - *Decorator: register `func` as the inferer for `source_name`.

Re-registration is a debug log + overwrite; the orchestrator picks
the LAST registered inferer for a given source name, so a test
can monkey-patch an inferer without monkey-patching the source.*
- `infer_relationship` (line 78) `def infer_relationship(observation, query, kg)` - *Dispatch an observation to its inferer (if any).

Returns True if an inferer ran, False otherwise. An inferer that
raises is logged at WARNING and returns False; the orchestrator
keeps going so one bad source doesn't poison the whole run.*
- `_infer_dns` (line 105) `def _infer_dns(observation, query, kg)`
- `_infer_crtsh` (line 114) `def _infer_crtsh(observation, query, kg)`
- `_infer_shodan` (line 122) `def _infer_shodan(observation, query, kg)`
- `_infer_greynoise` (line 134) `def _infer_greynoise(observation, query, kg)`
- `_infer_abuseipdb` (line 143) `def _infer_abuseipdb(observation, query, kg)`
- `_infer_whois` (line 152) `def _infer_whois(observation, query, kg)`
- `_infer_urlscan` (line 163) `def _infer_urlscan(observation, query, kg)`
- `_infer_phonebook` (line 175) `def _infer_phonebook(observation, query, kg)`
- `_infer_ipapi` (line 186) `def _infer_ipapi(observation, query, kg)`
- `_infer_otx` (line 195) `def _infer_otx(observation, query, kg)`
- `_infer_nvd` (line 208) `def _infer_nvd(observation, query, kg)`
- `__call__` (line 51) `def __call__(self, observation, query, kg)`
- `deco` (line 70) `def deco(func)`

#### `reliability_scoring.py`
**Path:** `estorides_core/reliability_scoring.py`
**File Doc:** *estorides_core.reliability_scoring ================================== Single source of truth for the ``confidence`` of an observation.  Replaces the two incompatible heuristics that used to live in :mod:`estorides_core.entity_extraction` (``min(1.0, c + 0.1)``) and :mod:`estorides_core.fusion_store` (``MAX(existing, incoming)``). The model is NATO Admiralty System (source reliability A-F x information credibility 1-6) extended with three operational factors: source type hierarchy (primary/secondary/tertiary), corroboration (``min(1, log10(1 + n))`` over independent sources), and freshness (exp half-life). Pure functions, no I/O, no logging, fail-loud on programmer input, fail-soft on operator input.  The public surface:  SourceReliability     enum str  {A, B, C, D, E, F} Credibility           enum int  {1..6} SourceType            enum str  {primary, secondary, tertiary} ConfidenceInput       dataclass, validated ConfidenceResult      dataclass frozen, JSON-serialisable compute_confidence    pure, O(1), no I/O merge_confidence      pure, O(1), no I/O reliability_from_name pure, never raises, never logs input source_type_from_name pure, never raises, never logs input*

**Classes:**
- `SourceReliability` (line 37) `class SourceReliability(str, Enum)` - *NATO Admiralty source-reliability rating (A-F).*
- `Credibility` (line 48) `class Credibility(int, Enum)` - *NATO Admiralty information-credibility rating (1-6).*
- `SourceType` (line 59) `class SourceType(str, Enum)` - *Source type hierarchy: primary > secondary > tertiary.

Orthogonal to NATO reliability: a primary source (official WHOIS) and a
secondary source (social media) can both be rated A, but the primary
contributes more weight because it is institutionally closer to the truth.*
- `ConfidenceInput` (line 243) `class ConfidenceInput` - *Validated input to :func:`compute_confidence`.*
- `ConfidenceResult` (line 263) `class ConfidenceResult` - *Auditable output of :func:`compute_confidence` / :func:`merge_confidence`.*

**Methods:**
- `_corroboration_weight` (line 280) `def _corroboration_weight(n)` - *``min(1, log10(1 + n))``.  0 sources → 0; 1 → 0.30; 9 → 1.0.*
- `_freshness_weight` (line 287) `def _freshness_weight(age_seconds, half_life_days)` - *Exponential decay.  age=0 → 1.0; one half-life → 0.5.*
- `_validate_score` (line 297) `def _validate_score(value, field_name)`
- `_clamp01` (line 302) `def _clamp01(value)` - *Clamp to the closed unit interval.*
- `compute_confidence` (line 312) `def compute_confidence(inp)` - *Compute the audit-trailed confidence score for one observation.

Pure: no I/O, no logging, no clock. Identical input identical output
bit-by-bit. Bounded to [0, 1]. The formula is:

    score = base * reliability_weight * credibility_weight
          * source_type_weight * corroboration_weight * freshness_weight*
- `merge_confidence` (line 350) `def merge_confidence(existing, new_observation)` - *Merge a new observation's confidence into an existing entity score.

The result is ``max(existing, new_score)`` clamped to ``[0, 1]``. An
unreliable new observation cannot raise a strongly-corroborated
existing one; a reliable new observation can lift a weakly-attested
one. The :class:`ConfidenceResult` exposes the weights of the *new*
observation so the audit trail is intact.*
- `reliability_from_name` (line 400) `def reliability_from_name(source_name)` - *Look up the reliability for a source by name; never raises.

Operator input is potentially adversarial. We do not log, do not
validate beyond ``lower().strip()``, and never raise. Unknown names
fall back to :data:`DEFAULT_RELIABILITY`.*
- `source_type_from_name` (line 415) `def source_type_from_name(source_name)` - *Look up the source type hierarchy for a source by name; never raises.

Operator input is potentially adversarial. Same contract as
:func:`reliability_from_name`: no logging, no raising, no ReDoS.
Unknown names fall back to :data:`DEFAULT_SOURCE_TYPE` (TERTIARY).*
- `reliability_weight` (line 430) `def reliability_weight(source_name, overrides)` - *Numeric reliability weight for a source name; never raises.

`overrides` maps a source name to an explicit reliability letter
(used by the recon-fusion config). An invalid override letter falls
back to C, matching the historical behaviour. Unknown names fall back
to :data:`RELIABILITY_WEIGHT` for :data:`DEFAULT_RELIABILITY`.*
- `reliability_weight_for_letter` (line 452) `def reliability_weight_for_letter(letter)` - *Numeric weight for a reliability letter (A-F); never raises.*
- `__post_init__` (line 253) `def __post_init__(self)`

#### `scope.py`
**Path:** `estorides_core/scope.py`
**File Doc:** *estorides_core.scope ===================== Bug-bounty scope classification.  Takes the assets a passive run surfaces (hosts, IPs, CIDRs) and decides, for each one, whether it is in-scope, out-of-scope, or unknown against a program's rules. Out-of-scope always wins so the operator never points active tooling at an excluded asset — the fastest way to get removed from a program.  Rule grammar (one rule per line, blank lines and `#` comments ignored):  *.example.com        wildcard host suffix (matches sub.example.com and example.com) example.com          exact host 192.0.2.0/24         CIDR network (IPv4 or IPv6) 198.51.100.7         single IP address re:^api[0-9]+\\.ex   regex (prefix ``re:``), matched against the raw asset  A single rules file may carry both lists, divided by a line whose content is ``## out-of-scope`` (case-insensitive); rules above the divider are in-scope, rules below are out-of-scope. The two lists can also be supplied independently when building a :class:`ScopeMatcher` directly.  The design is deliberately decoupled (SOLID): every grammar form is a small :class:`ScopeRule` with one ``matches`` method, rules are produced by an ordered list of detector factories (open/closed — add a form by appending one factory), and :class:`ScopeMatcher` only knows the out-beats-in precedence. No call site hard-codes a grammar.*

**Classes:**
- `ScopeRule` (line 89) `class ScopeRule(ABC)` - *A single scope predicate. Implementations match exactly one grammar.*
- `WildcardRule` (line 102) `class WildcardRule(ScopeRule)` - *`*.example.com` — the apex and any subdomain of it.*
- `ExactHostRule` (line 117) `class ExactHostRule(ScopeRule)` - *A single fully-qualified host, matched verbatim.*
- `CidrRule` (line 130) `class CidrRule(ScopeRule)` - *An IPv4/IPv6 network; matches any address inside it.*
- `RegexRule` (line 148) `class RegexRule(ScopeRule)` - *A compiled regex matched against the raw normalised asset.*
- `ScopeMatcher` (line 234) `class ScopeMatcher` - *Classifies assets against in-scope and out-of-scope rule sets.

Out-of-scope is evaluated first and wins outright, so an asset that
matches both a broad in-scope wildcard and a narrow out-of-scope rule
is reported as out-of-scope.*
- `ScopeReport` (line 344) `class ScopeReport` - *Classified assets plus the flat lists an operator pipes onward.*

**Functions:**
- `normalise_asset` (line 52) `def normalise_asset(raw)` - *Reduce a raw asset string to a comparable host or IP literal.

Strips an optional scheme, any path/query, a port, surrounding
whitespace and a trailing dot, and lowercases the result. A value that
is already a bare host or IP passes through unchanged (bar casing).*
- `is_ip` (line 79) `def is_ip(asset)` - *True when `asset` parses as a bare IPv4 or IPv6 address.*

**Methods:**
- `_wildcard_factory` (line 161) `def _wildcard_factory(text)`
- `_regex_factory` (line 168) `def _regex_factory(text)`
- `_cidr_factory` (line 179) `def _cidr_factory(text)`
- `_ip_factory` (line 188) `def _ip_factory(text)`
- `_exact_host_factory` (line 195) `def _exact_host_factory(text)`
- `parse_rule` (line 211) `def parse_rule(line)` - *Parse one rule line into a ScopeRule, or None for blank/comment/invalid.*
- `parse_rules` (line 223) `def parse_rules(lines)` - *Parse many rule lines, skipping blanks, comments and invalid entries.*
- `load_rules_file` (line 285) `def load_rules_file(path)` - *Build a matcher from a rules file, honouring the out-of-scope divider.

Lines above a ``## out-of-scope`` divider (case-insensitive) are
in-scope; lines below are out-of-scope. A file with no divider is all
in-scope.*
- `load_assets` (line 303) `def load_assets(path)` - *Read assets from a file: a discover surface JSON or a flat host list.

A JSON document is mined for assets in `domains`, `entities` (values of
host/ip types) and any top-level list of strings. Anything else is read
as one asset per line.*
- `_assets_from_json` (line 322) `def _assets_from_json(doc)` - *Extract candidate assets from a parsed discover/result JSON document.*
- `build_report` (line 371) `def build_report(matcher, assets)` - *Classify `assets` with `matcher` and return a :class:`ScopeReport`.*
- `write_flat_lists` (line 381) `def write_flat_lists(report, out_dir)` - *Write newline-delimited flat lists for piping into active tooling.

Produces `in_scope_hosts.txt`, `in_scope_ips.txt` and `unknown.txt`
under `out_dir`. Returns the map of label to written path.*
- `matches` (line 93) `def matches(self, asset)` - *True when `asset` (already normalised) is covered by this rule.*
- `describe` (line 97) `def describe(self)` - *Human-readable form of the rule, for reports and audit.*
- `matches` (line 107) `def matches(self, asset)`
- `describe` (line 112) `def describe(self)`
- `matches` (line 122) `def matches(self, asset)`
- `describe` (line 125) `def describe(self)`
- `matches` (line 135) `def matches(self, asset)`
- `describe` (line 143) `def describe(self)`
- `matches` (line 153) `def matches(self, asset)`
- `describe` (line 156) `def describe(self)`
- `__init__` (line 242) `def __init__(self, in_scope, out_of_scope)`
- `in_rules` (line 251) `def in_rules(self)`
- `out_rules` (line 255) `def out_rules(self)`
- `classify` (line 258) `def classify(self, raw_asset)` - *Return IN_SCOPE, OUT_OF_SCOPE or UNKNOWN for a single asset.*
- `partition` (line 269) `def partition(self, assets)` - *Bucket many assets, returning sorted, de-duplicated lists.

Keys are IN_SCOPE, OUT_OF_SCOPE and UNKNOWN. De-duplication is on
the normalised form so `HTTPS://Example.com/` and `example.com`
collapse to one entry.*
- `hosts` (line 352) `def hosts(self)` - *In-scope hostnames (everything in-scope that is not an IP).*
- `ips` (line 357) `def ips(self)` - *In-scope bare IP addresses.*
- `to_dict` (line 361) `def to_dict(self)`

#### `search_telemetry.py`
**Path:** `estorides_core/search_telemetry.py`
**File Doc:** *estorides.search_telemetry.  Single source of truth for the operator-facing telemetry surface: the search-in-progress model, the onboarding catalog (keyboard shortcuts and tips) and the brand/emoji integrity invariants.  The module is pure: no Flask import, no I/O, no mutable global state. The web layer injects ``SearchTelemetry.context()`` into the Jinja render so the server and the browser share one vocabulary; the test suite asserts the served chrome honours the invariants. Numbers, copy and the brand vocabulary all resolve from the frozen ``TelemetryConfig`` instance, never from scattered literals.*

**Classes:**
- `SearchTelemetryError` (line 37) `class SearchTelemetryError(Exception)` - *Base class for every error raised by this module.*
- `UnknownPhaseError` (line 41) `class UnknownPhaseError(SearchTelemetryError, KeyError)` - *Raised when a phase key is not part of the configured vocabulary.*
- `InvalidTelemetryConfigError` (line 45) `class InvalidTelemetryConfigError(SearchTelemetryError, ValueError)` - *Raised when a :class:`TelemetryConfig` violates a construction rule.*
- `KeyboardShortcut` (line 117) `class KeyboardShortcut` - *A single keyboard shortcut: the key chord and what it does.*
- `SplashTip` (line 125) `class SplashTip` - *A single onboarding tip: a short title and a one-line body.*
- `SearchPhase` (line 133) `class SearchPhase` - *A search lifecycle phase: a stable key, a human label and activity flag.

``active`` is ``True`` while work is in flight (the UI shows a spinner) and
``False`` for the resting states ``idle``, ``done`` and ``error``.*
- `ProgressView` (line 146) `class ProgressView` - *Immutable, render-ready snapshot of search progress.

Carries the clamped counters, the percentage, the human label and the ARIA
attributes a screen reader needs to announce a live progress region.*
- `TelemetryConfig` (line 177) `class TelemetryConfig` - *Frozen catalog: brand, tagline, shortcuts, tips and phase vocabulary.

``__post_init__`` enforces every invariant in spec/search_telemetry.md so an
invalid catalog can never reach the UI. All construction failures raise
:class:`InvalidTelemetryConfigError`.*
- `SearchTelemetry` (line 217) `class SearchTelemetry` - *Service over a :class:`TelemetryConfig`: progress math and catalog views.

Stateless and side-effect free; safe to instantiate or share per request.*

**Methods:**
- `disallowed_brands_in` (line 75) `def disallowed_brands_in(text)` - *Return the third-party brand tokens found in ``text``.

Matching is case-insensitive and word-boundary aware so an ordinary word
that merely contains a brand as a substring does not produce a false
positive. The returned tuple holds the canonical lowercase brand names in
first-seen order, de-duplicated.*
- `emoji_in` (line 89) `def emoji_in(text)` - *Return the emoji glyphs found in ``text``, de-duplicated in order.

Emoji are codepoints in the pictographic blocks (Miscellaneous Symbols,
Dingbats, regional indicators, the supplementary pictographic planes) and
the emoji variation selector. Geometric line-symbols outside those blocks
are permitted iconography and are not flagged.*
- `percent_encoded_emoji_in` (line 103) `def percent_encoded_emoji_in(text)` - *Return percent-encoded supplementary-plane emoji sequences in ``text``.

Catches an emoji smuggled into a ``data:`` URI (for example an emoji
favicon) as the UTF-8 lead bytes ``%F0%9F`` followed by two continuation
bytes. The returned tuple holds the matched sequences in first-seen order.*
- `_assert_clean` (line 167) `def _assert_clean(label)` - *Raise :class:`InvalidTelemetryConfigError` if any text leaks brand/emoji.*
- `_default_config` (line 309) `def _default_config()` - *Build the canonical Estorides telemetry catalog.*
- `__post_init__` (line 191) `def __post_init__(self)`
- `__init__` (line 223) `def __init__(self, config)`
- `shortcuts` (line 229) `def shortcuts(self)` - *Return the keyboard-shortcut catalog.*
- `tips` (line 233) `def tips(self)` - *Return the onboarding tips catalog.*
- `phases` (line 237) `def phases(self)` - *Return the search-phase vocabulary.*
- `phase` (line 241) `def phase(self, key)` - *Return the phase for ``key`` or raise :class:`UnknownPhaseError`.*
- `progress` (line 249) `def progress(self, completed, total, phase_key)` - *Compute a clamped, render-ready :class:`ProgressView`.

Out-of-range counters are clamped, never rejected; only an unknown
``phase_key`` raises (:class:`UnknownPhaseError`). When ``total`` is not
yet known (``<= 0``) and the phase is active the view is indeterminate.*
- `context` (line 288) `def context(self)` - *Return the JSON-serialisable catalog for template/JS injection.*

#### `socmint.py`
**Path:** `estorides_core/socmint.py`
**File Doc:** *estorides_core.socmint ====================== Social Media Intelligence (SOCMINT) module.  Provides: * PlatformInfo — metadata about a known social platform * ProfileMatch — a single platform profile match * SocialMediaProfile — aggregated cross-platform profile * SocialMediaInferer — correlates usernames across all known platforms  The inferer uses the source registry to determine which platforms are available, and the entity resolution layer to fuse profiles that share the same handle across different services.*

**Classes:**
- `PlatformInfo` (line 32) `class PlatformInfo` - *Metadata about a known social media platform for cross-referencing.*
- `ProfileMatch` (line 81) `class ProfileMatch` - *A single platform profile match for a username.*
- `SocialMediaProfile` (line 105) `class SocialMediaProfile` - *Aggregated social media profile for a username across all platforms.*
- `SocialMediaInferer` (line 203) `class SocialMediaInferer` - *Correlates usernames across social media platforms.

Pure: no I/O, no external dependencies. Uses the platform registry
to determine which platforms exist and which YAML sources back them.

The inferer provides:
  1. resolve(username) — build a SocialMediaProfile for a username
  2. discover_from_text(text) — extract cross-platform links from text*

**Methods:**
- `_extract_profile_urls` (line 164) `def _extract_profile_urls(text)` - *Extract social media profile URLs from a text blob.

Scans text for common social media URL patterns and returns a
dict of platform -> username for every match found.*
- `_confidence_for_platform_matches` (line 185) `def _confidence_for_platform_matches(platform_count, has_verified, has_keybase)` - *Compute cross-platform confidence based on evidence strength.

More platforms = higher confidence. A verified profile (Keybase/GitHub)
adds significant weight. The result is bounded to [0.0, 1.0].*
- `to_dict` (line 92) `def to_dict(self)`
- `to_dict` (line 119) `def to_dict(self)`
- `__init__` (line 214) `def __init__(self)`
- `resolve` (line 218) `def resolve(self, username, platforms)` - *Build a SocialMediaProfile for a username across all platforms.

Args:
    username: The handle to search for.
    platforms: Optional subset of platforms to check. If None,
              checks all known platforms.

Returns:
    A serialised SocialMediaProfile dict. When no platform could
    find the username, returns ``{"profiles": [], "no_matches": True}``.

This is a *speculative* resolution — it returns profile URL
templates for each platform. The actual hit/miss determination
happens when the YAML sources execute their HTTP calls.*
- `discover_from_text` (line 311) `def discover_from_text(self, text)` - *Extract social media profiles from a text blob.

Scans text for URLs pointing to known social platforms and
returns a dict of platform -> username for every match.

This is useful for enriching observations — a GitHub profile
that mentions a Twitter handle creates a cross-platform link.*
- `platform_list` (line 344) `def platform_list(self)` - *Return the full platform registry as a serialisable list.*

#### `source_health_monitoring.py`
**Path:** `estorides_core/source_health_monitoring.py`
**File Doc:** *estorides_core.source_health_monitoring ======================================= Per-source operational health tracking.  Computes health status (healthy/degrading/stale/unknown), composite health score, and a dashboard of hot vs degrading sources from raw fetch counters. Pure functions: no I/O, no logging, deterministic.  Public surface::  SourceHealthStatus  enum  {healthy, degrading, stale, unknown} SourceHealthInput   dataclass, validated SourceHealthResult  dataclass frozen, JSON-serialisable SourceHealthConfig  dataclass, validated HealthDashboard     dataclass frozen, JSON-serialisable compute_health      pure, O(1) build_dashboard     pure, O(n)*

**Classes:**
- `SourceHealthStatus` (line 31) `class SourceHealthStatus(str, Enum)` - *Operational health classification for an OSINT source.*
- `SourceHealthConfig` (line 42) `class SourceHealthConfig` - *Thresholds for source health classification.

Every field has a corresponding ``ESTORIDES_HEALTH_*`` environment
variable override at import time. The defaults are conservative:
three failures in ten = degrading; a week offline = stale.*
- `SourceHealthInput` (line 98) `class SourceHealthInput` - *Raw per-source data for health computation.

All fields are required; the caller (orchestrator, fusion analytics)
extracts these from the fusion store or its own tracking.*
- `SourceHealthResult` (line 134) `class SourceHealthResult` - *Health assessment for a single source.*
- `DashboardSummary` (line 160) `class DashboardSummary` - *Aggregate dashboard statistics.*
- `HealthDashboard` (line 173) `class HealthDashboard` - *Grouped health view: hot sources, degrading sources, aggregate stats.*

**Methods:**
- `_clamp01` (line 202) `def _clamp01(value)`
- `_classify` (line 210) `def _classify(success_rate, avg_latency_ms, freshness_hours, fetch_count, config)` - *Classify a source's health status based on thresholds.*
- `compute_health` (line 235) `def compute_health(inp, config)` - *Compute the health assessment for a single source.

Pure: no I/O, no logging, deterministic. The formula is::

    success_weight = success_rate ^ 2
    latency_weight = clamp(1 - avg_latency_s / degrading_latency_s, 0, 1)
    freshness_weight = clamp(1 - freshness_hours / stale_hours, 0, 1)
    health_score = 0.5 * success_weight + 0.3 * latency_weight + 0.2 * freshness_weight*
- `build_dashboard` (line 295) `def build_dashboard(records, config)` - *Build a health dashboard from per-source health inputs.

Groups sources into hot (healthy), degrading (degrading + stale), and
unknown lists. Computes aggregate summary statistics.*
- `__post_init__` (line 58) `def __post_init__(self)`
- `__post_init__` (line 112) `def __post_init__(self)`
- `to_dict` (line 146) `def to_dict(self)`
- `to_dict` (line 184) `def to_dict(self)`

#### `source_loader.py`
**Path:** `estorides_core/source_loader.py`
**File Doc:** *estorides_core.source_loader ============================ Loads all YAML sources, normalises the schema, and provides a registry the rest of the engine can iterate over.*

**Classes:**
- `Source` (line 22) `class Source(dict)` - *A source is a YAML-defined OSINT data provider.

Stored as a dict for JSON-serialisation convenience, but exposes
attribute access for ergonomic call sites.*
- `SourceRegistry` (line 38) `class SourceRegistry` - *Loads YAML sources from the sources/ directory and exposes them by name.*

**Methods:**
- `__init__` (line 28) `def __init__(self, data)`
- `__getattr__` (line 31) `def __getattr__(self, key)`
- `__init__` (line 41) `def __init__(self, sources_dir)`
- `load` (line 47) `def load(self)`
- `_load_file` (line 71) `def _load_file(self, path)`
- `_normalise` (line 111) `def _normalise(self, raw)`
- `get` (line 200) `def get(self, name)`
- `all` (line 203) `def all(self)`
- `by_category` (line 206) `def by_category(self, category)`
- `categories` (line 209) `def categories(self)`
- `names` (line 212) `def names(self)`
- `filter` (line 215) `def filter(self)` - *Return sources matching the given predicates.

`max_contact` keeps only sources whose contact class is at or below
the given ceiling (e.g. "none" for a passive-only run, "broker" to
also allow third-party probes). Sources with an unknown contact
class are treated as the most exposing and thus excluded by any
ceiling below `active`.*
- `_category_dir_name` (line 237) `def _category_dir_name(self, category)` - *Derive a filesystem-safe directory name from a category label.

E.g. ``"06. Breach Intelligence"`` → ``"06_breach_intelligence"``.*
- `_source_path` (line 253) `def _source_path(self, name, category)` - *Derive the filesystem path for a source based on its name and category.*
- `_find_source_file` (line 259) `def _find_source_file(self, name)` - *Locate a source file on disk by name, scanning all category dirs.*
- `write_source_file` (line 272) `def write_source_file(self, data)` - *Write a source dict to the correct YAML file, overwriting if exists.

This is a pure file-write operation — no normalisation, no registry
update. The caller is responsible for reloading the registry if needed.
Returns the path written.*
- `delete_source_file` (line 342) `def delete_source_file(self, name)` - *Delete a source file by name. Raises KeyError if not found on disk.*
- `summary` (line 351) `def summary(self)` - *Compact summary used by /api/status.*

#### `sqlite_store.py`
**Path:** `estorides_core/sqlite_store.py`
**File Doc:** *estorides_core.sqlite_store =========================== Shared plumbing for the hand-rolled SQLite stores.  Four stores (`monitoring.WatchStore`, `cases.CaseStore`, `entity_store.EntityStore`, `fusion_store.FusionStore`) used to carry byte-identical copies of:  * `__init__` — mkdir + lock + `sqlite3.connect(check_same_thread=False, isolation_level=None)` + WAL/foreign-keys pragmas + schema init; * `_init_schema` — execute every DDL statement under the lock; * `_tx` — `BEGIN` / `COMMIT` / `ROLLBACK` inside the lock; * `close` — swallow-and-close.  This module is the single source of truth for that boilerplate. A store subclasses :class:`SqliteStore`, declares `_DDL` and `_DEFAULT_PATH`, and overrides only its own queries.  Also exports :class:`DictMixin` for the 29 dataclasses whose `to_dict` was literally `asdict(self)`.*

**Classes:**
- `SqliteStore` (line 34) `class SqliteStore` - *Thread-safe SQLite base: one serialised connection behind a lock.

Contract:
  * ``_DDL``          — list/tuple of schema statements, executed once.
  * ``_DEFAULT_PATH`` — path used when the constructor gets ``None``.
  * ``_PRAGMAS``      — connection pragmas applied before schema init.*
- `DictMixin` (line 109) `class DictMixin` - *`to_dict()` for dataclasses that map 1:1 to a JSON object.*

**Methods:**
- `__init__` (line 50) `def __init__(self, path)`
- `_init_schema` (line 66) `def _init_schema(self)`
- `_tx` (line 72) `def _tx(self)`
- `close` (line 82) `def close(self)`
- `__del__` (line 89) `def __del__(self)`
- `to_dict` (line 112) `def to_dict(self)`

#### `ssrf_guard.py`
**Path:** `estorides_core/ssrf_guard.py`
**File Doc:** *estorides_core.ssrf_guard ========================= SSRF / SSRF-rebound protection for outbound HTTP.  The OSINT engine speaks to user-specified hosts (a domain, a URL from a paste, a link from a Telegram channel). Without filtering, a malicious or careless query can redirect our client at:  * RFC1918 private space              (10/8, 172.16/12, 192.168/16) * Loopback                           (127/8, ::1) * Link-local / cloud metadata        (169.254/16 — AWS, GCP, Azure IMDS) * CGNAT / Tailscale                  (100.64/10) * Multicast / reserved               (224/4, 240/4, 0/8, 255.255.255.255) * Documentation / benchmark          (192.0.2/24, 198.51.100/24, 203.0.113/24) * IPv6 link-local                    (fe80::/10) * IPv6 unique-local                  (fc00::/7) * IPv4-mapped IPv6                   (::ffff:0:0/96)  We block at TWO layers:  1. Canonicalise the literal host the source YAML gave us. Reject dotted forms that land in a reserved range and any IPv6 in a reserved prefix. 2. For hostnames, resolve A + AAAA at fetch time and reject if *any* answer lands in a reserved range. This is the TOCTOU-resistant layer that catches a DNS rebinding attack where the YAML says `attacker.com` but the resolver returns `169.254.169.254`.  Pinning the IP at the socket layer (the third leg of true TOCTOU defence) is intentionally out of scope: aiohttp doesn't expose socket-level*

**Classes:**
- `GuardResult` (line 99) `class GuardResult`
- `SSRFError` (line 269) `class SSRFError(ValueError)` - *Raised when an outbound URL fails the SSRF guard.*

**Methods:**
- `_is_blocked_v4` (line 110) `def _is_blocked_v4(ip)`
- `_is_blocked_v6` (line 114) `def _is_blocked_v6(addr)` - *Match an IPv6 textual address against the prefix table.

Lower-cased, leading zeros collapsed, no scope-id parsing required.*
- `_normalise_host` (line 126) `def _normalise_host(host)` - *Lowercase, strip brackets from IPv6 literals, return None if empty.*
- `_is_host_in_blocked_literal` (line 134) `def _is_host_in_blocked_literal(host)` - *If `host` is a literal IP in a blocked range, return a reason string.
Otherwise return None.*
- `_resolve` (line 155) `def _resolve(host)` - *Resolve `host` to its A + AAAA records. Empty on failure.*
- `_matches_allowlist` (line 172) `def _matches_allowlist(host, allowlist)` - *Return True if `host` matches any entry in the allowlist.

An entry like `osiris.example.com` matches the host itself and any
subdomain. An entry like `*` matches everything (escape hatch).*
- `_load_allowlist` (line 189) `def _load_allowlist()`
- `check_url` (line 194) `def check_url(url)` - *Validate a URL for outbound fetch.

Args:
    url: the URL string the source YAML wants us to hit.
    resolve: when True (default) also resolve hostnames and reject
        if any answer lands in a reserved range. Disable only in
        tests with mocked DNS.

Returns:
    GuardResult with allowed/reason. Use as a bool.*
- `assert_safe` (line 262) `def assert_safe(url)` - *Raise SSRFError if `url` is not safe to fetch.*
- `__bool__` (line 105) `def __bool__(self)`

#### `supply_chain.py`
**Path:** `estorides_core/supply_chain.py`

**Classes:**
- `ThirdParty` (line 54) `class ThirdParty`
- `SharedInfra` (line 66) `class SharedInfra`
- `Relationship` (line 78) `class Relationship`
- `SupplyChainResult` (line 89) `class SupplyChainResult`

**Methods:**
- `detect_mx_provider` (line 106) `def detect_mx_provider(mx_records)`
- `detect_ns_provider` (line 114) `def detect_ns_provider(ns_records)`
- `detect_cdn` (line 122) `def detect_cdn(cname)`
- `analyse_third_parties` (line 129) `def analyse_third_parties(third_parties, subsidiaries)`
- `detect_shared_infrastructure` (line 140) `def detect_shared_infrastructure(asn)`
- `to_dict` (line 61) `def to_dict(self)`
- `to_dict` (line 73) `def to_dict(self)`
- `to_dict` (line 84) `def to_dict(self)`
- `to_dict` (line 96) `def to_dict(self)`

#### `system_app_sources.py`
**Path:** `estorides_core/system_app_sources.py`
**File Doc:** *estorides_core.system_app_sources ================================= Kali OSINT CLI tools as first-class sources (`kind: system_app`).  A system_app source is a YAML-declared local binary executed through the existing :mod:`estorides_core.tool_runner` sandbox (allowlist, no shell, injection blocking, timeouts, output caps) whose output is parsed and fed into the **same** aggregation pipeline as HTTP sources: observations → entity extraction → fusion → reliability scoring → recon tiers.  The YAML schema differs from HTTP sources (`tool.binary` + `tool.args` instead of `tool.url` + `tool.params`) — one registry, two origin kinds, zero special-casing downstream.  Public surface:  KIND_SYSTEM_APP        "system_app" origin kind constant SystemAppResult        frozen dataclass, JSON-serialisable is_system_app          kind/binary predicate tool_available         shutil.which wrapper (monkeypatchable) render_args            {query}/{outdir} template substitution, pure parse_tool_output      parser dispatch with never-raise contract execute                full run: sandbox + parse + audit metadata TOOL_PARSER_NAMES      registered tool-output parsers (for fuzzing)*

**Classes:**
- `SystemAppResult` (line 57) `class SystemAppResult` - *Normalised outcome of one system_app tool invocation.

Mirrors the contract in ``spec/system_app_sources.md``: failures are
values, never exceptions.*

**Methods:**
- `is_system_app` (line 81) `def is_system_app(source)` - *True when a source dict is a system_app (or has a binary tool).*
- `tool_available` (line 91) `def tool_available(binary)` - *Resolve a binary on the filesystem (shutil.which).*
- `render_args` (line 96) `def render_args(args, query, outdir)` - *Substitute ``{query}``/``{outdir}`` placeholders in an args template.

Unknown placeholders are left verbatim (same contract as the HTTP
``_safe_format``). Non-string template elements are programmer error
and raise ``ValueError`` — a silently stringified int is a typo that
must be caught at source-authoring time, not at exec time.*
- `_read_capped` (line 121) `def _read_capped(path, cap)` - *Read a file, capped at ``cap`` bytes, UTF-8 with replacement.*
- `_loads_lenient` (line 131) `def _loads_lenient(text)` - *Best-effort JSON parse of tool output (tolerates log-prefix noise).*
- `_line_filter_parser` (line 150) `def _line_filter_parser()` - *Factory for noise-reducing line parsers (one definition, N tools).

Keep-lines must match *every* supplied condition; ``strip_prefixes``
are removed from surviving lines so the operator sees data, not noise.*
- `parse_amass_json` (line 193) `def parse_amass_json(payload)` - *amass ``-json`` output: one JSON object per line (DNS + infra).*
- `parse_maigret_json` (line 225) `def parse_maigret_json(payload)` - *maigret ``--json simple``: one object keyed by site name.*
- `parse_phoneinfoga_json` (line 247) `def parse_phoneinfoga_json(payload)` - *phoneinfoga v2 ``scan``: a single JSON object (possibly after logs).*
- `parse_sherlock_text` (line 260) `def parse_sherlock_text(payload)`
- `parse_holehe_text` (line 265) `def parse_holehe_text(payload)`
- `parse_wafw00f_text` (line 270) `def parse_wafw00f_text(payload)`
- `parse_sublist3r_lines` (line 275) `def parse_sublist3r_lines(payload)`
- `parse_dnsrecon_text` (line 280) `def parse_dnsrecon_text(payload)`
- `parse_dnsenum_text` (line 285) `def parse_dnsenum_text(payload)`
- `parse_fierce_text` (line 292) `def parse_fierce_text(payload)`
- `parse_dmitry_text` (line 297) `def parse_dmitry_text(payload)`
- `parse_urlcrazy_text` (line 302) `def parse_urlcrazy_text(payload)`
- `parse_metagoofil_text` (line 307) `def parse_metagoofil_text(payload)`
- `parse_whatweb_text` (line 312) `def parse_whatweb_text(payload)`
- `parse_theharvester_text` (line 317) `def parse_theharvester_text(payload)`
- `parse_usufy_text` (line 322) `def parse_usufy_text(payload)`
- `parse_mailfy_text` (line 327) `def parse_mailfy_text(payload)`
- `parse_phonefy_text` (line 332) `def parse_phonefy_text(payload)`
- `parse_searchfy_text` (line 337) `def parse_searchfy_text(payload)`
- `parse_tool_output` (line 365) `def parse_tool_output(source_name, parser_name, data)` - *Parse tool output with the declared parser; never raises.

A parser that returns a non-list is wrapped; an empty/None parse falls
back to the generic entity scanner over the raw text so a tool with a
broken/unknown parser still yields entities.*
- `execute` (line 396) `def execute(source, query)` - *Execute one system_app source through the tool_runner sandbox.

Failures (missing binary, not allowlisted, wrong platform, injection,
timeout, crash) are ``SystemAppResult`` values — never exceptions.
``{outdir}`` renders into a fresh private TemporaryDirectory that is
removed after parsing.*
- `to_dict` (line 76) `def to_dict(self)`
- `parser` (line 165) `def parser(payload)`
- `fail` (line 418) `def fail(code, message)`
- `repl` (line 109) `def repl(m)`

#### `tech_fingerprint.py`
**Path:** `estorides_core/tech_fingerprint.py`

**Classes:**
- `Tech` (line 89) `class Tech`
- `TechFingerprintResult` (line 102) `class TechFingerprintResult`

**Methods:**
- `fingerprint` (line 115) `def fingerprint(headers, html, cookies, status)`
- `to_dict` (line 97) `def to_dict(self)`
- `to_dict` (line 107) `def to_dict(self)`
- `_add` (line 129) `def _add(name, category, version, source, confidence)`

#### `tool_install.py`
**Path:** `estorides_core/tool_install.py`
**File Doc:** *estorides_core.tool_install =========================== One-click installation for missing Kali/OSINT CLI tools, modelled on the LazyOwn lazyaddon mechanism (`lazyaddons/*.yaml`) and wired into the GUI's results section: when a source fails with ``TOOL_NOT_FOUND`` the UI offers an "Install tool" button that calls this module.  Each installable tool has a YAML recipe in ``tool_recipes/<name>.yaml`` with the same shape as a LazyOwn lazyaddon:  name: sherlock description: ... os: any apt: sherlock            # optional: Debian/Kali package name git:                     # optional: git + install_command fallback repo_url: https://github.com/sherlock-project/sherlock.git install_path: sherlock install_command: pip install -r requirements.txt  Install methods (tried in order when the binary is still missing):  * ``apt`` — ``run0 apt-get update`` + ``run0 apt-get install -y <pkg>``. * ``git`` — ``git clone <repo_url> <install_path>`` (as the operator, no elevation) then ``<install_command>`` inside the clone (elevated only when it performs a system-wide install, e.g. ``pip install``).  Privilege elevation ------------------- Estorides is a GUI app; user management should be graphical. The default*

**Classes:**
- `InstallRecipe` (line 81) `class InstallRecipe` - *One tool's install recipe, loaded from ``tool_recipes/<name>.yaml``.

Mirrors the LazyOwn lazyaddon ``tool`` block. ``apt`` is tried first;
``git`` is the fallback. At least one of the two must be present.*
- `InstallResult` (line 103) `class InstallResult` - *Outcome of one ``install_tool`` invocation. Failures are values.*
- `ToolStatus` (line 278) `class ToolStatus` - *One binary's install/readiness state for the doctor report.*

**Methods:**
- `_elevate` (line 117) `def _elevate(cmd)` - *Prepend an elevation wrapper when required.

Priority: ``run0`` (graphical polkit prompt — the GUI-friendly default),
then ``sudo``, then nothing when already root.*
- `_run` (line 132) `def _run(cmd)` - *Run a subprocess as an argument list, capping output.

Returns (exit_code, stdout, stderr). Never uses a shell: install
commands are shlex-split by the caller and run argument-by-argument,
so a mangled recipe cannot inject a subcommand.*
- `_check_shell_command` (line 148) `def _check_shell_command(command)` - *Reject hostile tokens in a trusted recipe's install_command.*
- `_needs_elevation` (line 154) `def _needs_elevation(command)` - *True when an install_command writes system-wide and needs root.*
- `is_valid_recipe_name` (line 169) `def is_valid_recipe_name(name)` - *True when ``name`` is a safe recipe/tool identifier (no separators).*
- `is_valid_binary` (line 174) `def is_valid_binary(name)` - *True when ``name`` is a plausible bare binary name (no path).*
- `_recipe_table` (line 179) `def _recipe_table()` - *Map recipe stem to its file from the directory listing.

The returned paths come from filesystem enumeration, never from
caller input: looking up a hostile name yields ``None`` instead of
a path built out of the name itself (CodeQL #48/#49/#52).*
- `_recipe_path` (line 193) `def _recipe_path(name)`
- `load_recipe` (line 210) `def load_recipe(name)` - *Load a tool recipe from ``tool_recipes/<name>.yaml`` (or ``None``).

A malformed recipe is logged and treated as absent so one bad file can
never break the whole registry. An invalid (traversal) name is likewise
treated as absent so a hostile ``<name>`` URL segment can never cause a
filesystem read outside ``TOOL_RECIPES_DIR``. Names outside the shipped
recipe allowlist are treated as absent before any path is built
(CodeQL #48/#49/#52): only directory-listed stems ever reach the
filesystem.*
- `recipe_available` (line 254) `def recipe_available(name)` - *True when a recipe exists for ``name`` (the UI gates the button on this).*
- `tool_available` (line 259) `def tool_available(binary)` - *True when the binary resolves on PATH (mirrors system_app_sources).*
- `list_recipes` (line 270) `def list_recipes()` - *Names of all tool recipe files in ``tool_recipes/`` (sorted).*
- `_system_app_binaries` (line 297) `def _system_app_binaries(sources_dir)` - *Map binary -> source names from the system_app YAML catalog.*
- `doctor` (line 321) `def doctor(sources_dir)` - *Read-only readiness report over system_app binaries + recipes.

Never installs, never elevates, never raises on malformed YAML:
every failure mode degrades to an entry, not an exception.*
- `_install_apt` (line 358) `def _install_apt(recipe)` - *Install an apt package via the elevation wrapper (run0/sudo).*
- `_tools_root` (line 371) `def _tools_root()`
- `_install_git` (line 375) `def _install_git(recipe)` - *Clone the repo (as operator) and run install_command (elevated if system-wide).*
- `install_tool` (line 414) `def install_tool(tool_name)` - *Install a missing tool from its recipe (if any).

If ``binary`` is given (the executable name from ``meta.tool_binary``),
the install is skipped when it already resolves on PATH unless ``force``.*
- `_verify` (line 506) `def _verify(binary)` - *Post-install check: can the binary now be resolved?*
- `main` (line 516) `def main(argv)` - *Minimal CLI: ``python -m estorides_core.tool_install <tool> [--force]``.*
- `has_apt` (line 95) `def has_apt(self)`
- `has_git` (line 98) `def has_git(self)`
- `to_dict` (line 113) `def to_dict(self)`
- `to_dict` (line 287) `def to_dict(self)`

#### `tool_runner.py`
**Path:** `estorides_core/tool_runner.py`

**Classes:**
- `ToolError` (line 17) `class ToolError(Exception)`
- `ToolNotAllowedError` (line 21) `class ToolNotAllowedError(ToolError)`
- `ToolInjectionError` (line 25) `class ToolInjectionError(ToolError)`
- `ToolNotFoundError` (line 29) `class ToolNotFoundError(ToolError)`
- `ToolTimeoutError` (line 33) `class ToolTimeoutError(ToolError)`
- `ToolResult` (line 38) `class ToolResult`
- `ToolErrorResult` (line 86) `class ToolErrorResult`

**Methods:**
- `_check_injection` (line 97) `def _check_injection(args)`
- `_resolve_binary` (line 107) `def _resolve_binary(tool_name)`
- `_check_allowlist` (line 114) `def _check_allowlist(tool_name)`
- `_parse_entities_generic` (line 121) `def _parse_entities_generic(stdout, tool_name)`
- `run_tool` (line 156) `def run_tool(tool_name, args, target, timeout, max_output_bytes, cwd)`
- `to_dict` (line 51) `def to_dict(self)`
- `from_failure` (line 59) `def from_failure(cls, tool_name, error_code, error_message, duration_s, exit_code, stdout, stderr, parsed_entities)`
- `to_dict` (line 93) `def to_dict(self)`

#### `transforms.py`
**Path:** `estorides_core/transforms.py`
**File Doc:** *estorides_core.transforms ========================= Maltego-style transform registry. A *transform* takes one entity `(type, value)` and returns related `nodes`/`links` in the same shape as :mod:`estorides_core.intel_resolver`, so the web layer and the D3 graph can merge the result with the existing expansion pipeline.  Transforms are grouped into the four-stage intelligence pipeline the operator walks a selector through:  data -> information -> intelligence -> counter_intelligence  Most transforms are thin filters over a single cross-feed resolution (`resolver.resolve`), which already fans a selector out across Wikidata, IP-API, OFAC and VirusTotal relationships. A few wrap the keyless Osiris probes (BGP, breach leaks, GitHub) and adapt their raw payloads into the node/link shape.  Public surface::  from estorides_core.transforms import registry registry.for_type("ip")                 # -> [{"id","label","tier",...}, ...] registry.run("ip_to_sanctions", "ip", "1.2.3.4")  Transforms can also be added without code: drop a ``*.yaml`` file in ``transforms/`` (same ease as ``sources/``)::  id: my_pivot label: My pivot*

**Classes:**
- `Transform` (line 73) `class Transform` - *One named transform applicable to a set of entity types.*
- `TransformRegistry` (line 227) `class TransformRegistry` - *Holds every transform and dispatches by id.*

**Methods:**
- `_empty` (line 98) `def _empty(root_type, value)`
- `_resolver_filtered` (line 102) `def _resolver_filtered(ent_type, value, relations)` - *Resolve `(ent_type, value)` and keep only links whose relation is
in `relations` (or every link when `relations` is None). The root
node plus any node touched by a kept link is returned.*
- `_filter_runner` (line 123) `def _filter_runner(relations)`
- `_norm` (line 132) `def _norm(s)`
- `_osiris` (line 137) `def _osiris()`
- `_run_bgp` (line 145) `def _run_bgp(ent_type, value)`
- `_run_leaks` (line 171) `def _run_leaks(ent_type, value)`
- `_run_github` (line 196) `def _run_github(ent_type, value)`
- `_str_list` (line 324) `def _str_list(raw)`
- `_static_runner` (line 334) `def _static_runner(nodes_tpl, links_tpl)` - *Fixed nodes/links with ``{query}`` substitution (zero I/O).*
- `_transform_from_yaml` (line 364) `def _transform_from_yaml(raw, origin)` - *Validate one YAML mapping into a Transform (None = skip).*
- `iter_sse_events` (line 408) `def iter_sse_events(transform_id, ent_type, value, runner)` - *Yield ``(kind, payload)`` tuples for the SSE stream endpoint.

Kinds: ``node`` / ``link`` per item, then ``done`` with counts, or
``error`` when the run fails. `runner` override exists for tests.*
- `_T` (line 443) `def _T(id, label, tier, applies, runner, description, output_types, cost)`
- `summary` (line 85) `def summary(self)`
- `run` (line 124) `def run(ent_type, value)`
- `__init__` (line 230) `def __init__(self)`
- `register` (line 233) `def register(self, t)`
- `for_type` (line 236) `def for_type(self, ent_type)`
- `run` (line 248) `def run(self, transform_id, ent_type, value)`
- `load_yaml_dir` (line 282) `def load_yaml_dir(self, directory)` - *Register every transform declared in ``*.yaml`` under `directory`.

Same ease as ``sources/``: one file per pivot, multi-doc / list
docs supported. Broken files are skipped fail-soft; duplicates
overwrite with a warning. Returns the number of transforms loaded.*
- `run` (line 337) `def run(ent_type, value)`
- `sub` (line 340) `def sub(s, depth)`

#### `transliteration.py`
**Path:** `estorides_core/transliteration.py`
**File Doc:** *estorides_core.transliteration ============================== Cross-script name normalisation for entity resolution.  State-level person/organisation matching lives or dies on names that arrive in different writing systems: a sanctions list spells a target in Cyrillic, a leak spells him in Latin, a news article in Arabic. This module folds all of them onto a single comparable Latin (ASCII) skeleton so the resolver can score them against each other.  It is deliberately stdlib-only (no `unidecode`, no `transliterate`) so it adds zero hard dependencies, consistent with the rest of the engine. The maps are pragmatic romanisations (BGN/PCGN-leaning for Cyrillic/Greek, ALA-LC-leaning for Arabic), not bit-exact standards; for fuzzy name matching, recall matters more than scholarly fidelity.  Two representations are produced:  * ``to_latin(text)`` — best-effort Latin transliteration, lowercased and stripped of diacritics. ``"Владимир" -> "vladimir"``. * ``consonant_skeleton(text)`` — the Latin form with vowels removed, so a vowelless Arabic spelling can still meet a fully-vowelled Latin one. Arabic script omits short vowels, so ``"محمد"`` romanises to ``"mhmd"`` while the Latin source has ``"muhammad"``; comparing consonant skeletons (``"mhmd"`` vs ``"mhmd"``) recovers the match.*

**Functions:**
- `_strip_diacritics` (line 76) `def _strip_diacritics(text)` - *Drop combining marks via NFKD decomposition.

Folds ``é`` -> ``e``, ``ü`` -> ``u``, full-width forms to ASCII, and so
on. Characters that have no compatibility decomposition pass through
unchanged and are handled by the per-script map instead.*
- `to_latin` (line 87) `def to_latin(text)` - *Return a lowercased, diacritic-free Latin transliteration.

The pipeline is: casefold -> NFKD diacritic strip (so accented Greek
and Latin fold to their base letters) -> per-character script map
(Cyrillic/Greek/Arabic) -> keep only ``[a-z0-9 ]``. Casefolding and
stripping run *before* the map so that uppercase and accented source
letters reach the lowercase, accent-free keys the map is written for.
Whitespace is collapsed to single spaces and the result is trimmed.
Non-mappable characters (e.g. unmapped CJK) are dropped, which is the
safe failure mode for fuzzy matching.*
- `consonant_skeleton` (line 112) `def consonant_skeleton(text)` - *Return the Latin transliteration with vowels and spaces removed.

This is the vowel-insensitive comparison key. Abjad scripts (Arabic,
Hebrew) routinely omit short vowels, so two spellings of the same name
can only be reconciled on their consonant skeletons. The first
character is preserved even if it is a vowel, because name-initial
vowels are usually written and carry signal.

Adjacent duplicate letters are collapsed so that gemination written as
a doubled Latin consonant (``Muhammad`` -> ``mhmmd``) reconciles with a
script that marks it with a diacritic instead of doubling the letter
(Arabic ``محمد`` -> ``mhmd``).*
- `is_non_latin` (line 139) `def is_non_latin(text)` - *True if any character is outside the Basic Latin / Latin-1 range.

Used by the resolver to decide whether the cross-script path is worth
taking for a given value before paying for transliteration of both
sides of a comparison.*

#### `validation.py`
**Path:** `estorides_core/validation.py`
**File Doc:** *estorides_core.validation ========================= Input validation for free-form queries.  OSINT platforms ingest user-controlled text and turn it into network requests. A bare string is a free pass for: control characters, billion- laughing-doesn't-laugh payloads, ReDoS-inducing regex inputs, billion- byte blobs, and "clever" Unicode lookalikes that break downstream parsers. This module is the first line of defence: every public entry point (`api_run`, the CLI `run` subcommand) should pass the user query through `validate_query` before it touches a source.  Rules:  * Trim, collapse internal whitespace, strip control chars. * Cap length: 1-512 chars. Long enough for an indictment, short enough that no single run can become a DoS. * Reject ASCII control chars (except space, tab). * Reject Unicode bidi overrides (U+202A-202E, U+2066-2069) — these are the classic "swap the file extension" phishing trick and they have no business in an OSINT query. * Detect query type via the existing detector and require it to be one of the recognised shapes; reject anything that smells like a free-form text dump. * Return a typed `Query` object so callers don't have to redo the detection step.*

**Classes:**
- `QueryValidationError` (line 55) `class QueryValidationError(ValueError)` - *Raised when a query fails validation. The reason is in `.reason`.*
- `Query` (line 63) `class Query` - *A validated, normalised query string.*

**Methods:**
- `_strip_and_collapse` (line 73) `def _strip_and_collapse(text)`
- `validate_query` (line 85) `def validate_query(raw)` - *Validate and normalise a user query string.

Args:
    raw: the user-supplied query, exactly as received.
    max_length: hard cap on the normalised length. Default 512.

Raises:
    QueryValidationError if the query is empty, oversized, contains
    forbidden characters, or resolves to a type the engine cannot
    dispatch on.

Returns:
    A `Query` with the normalised text and detected type.*
- `__init__` (line 57) `def __init__(self, reason, message)`
- `__str__` (line 69) `def __str__(self)`

#### `vuln_correlation.py`
**Path:** `estorides_core/vuln_correlation.py`

**Classes:**
- `DefaultCred` (line 14) `class DefaultCred`
- `VulnEntry` (line 24) `class VulnEntry`
- `VulnCorrelationResult` (line 43) `class VulnCorrelationResult`

**Methods:**
- `_parsed_version` (line 141) `def _parsed_version(version)`
- `_version_in_range` (line 153) `def _version_in_range(version, v_start, v_end)`
- `lookup_cve_for_tech` (line 169) `def lookup_cve_for_tech(tech_name, version)`
- `correlate_technologies` (line 202) `def correlate_technologies(technologies)`
- `compute_attack_readiness` (line 228) `def compute_attack_readiness(vulnerabilities)`
- `to_dict` (line 19) `def to_dict(self)`
- `to_dict` (line 38) `def to_dict(self)`
- `to_dict` (line 52) `def to_dict(self)`

#### `web_security.py`
**Path:** `estorides_core/web_security.py`
**File Doc:** *estorides_core.web_security ===========================  Production-grade web hardening helpers used by the Flask app factory.  Responsibilities ---------------- * Security headers on every response (CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, HSTS when behind TLS). * Optional CORS allowlist (the platform is a local single-user tool by default — CORS is opt-in, not opt-out, so the default is a tight same-origin policy). * A defensive request-size guard that rejects oversized JSON / form bodies before they reach a route handler (the rest of the app trusts `request.get_json()` blindly). * A Werkzeug-debugger kill-switch. Production deployments must never expose the interactive debugger console — this helper refuses to register routes when the dev debugger is on, and logs the attempt at WARNING level. * A bearer-token auth gate (`require_auth`) for sensitive endpoints. When the operator sets `ESTORIDES_AUTH_TOKEN`, the sensitive `/api/*` surface stops trusting anonymous callers; the legitimate UI gets the token from a `<meta name="estorides-auth-token">` tag rendered into `index.html` and includes it as a header on every fetch.  When `ESTORIDES_AUTH_TOKEN` is unset, the system **auto-generates** a random 64-hex-char token on startup and prints it to the terminal. This ensures API abuse protection is always on — no login page needed, no explicit registration.  Why a dedicated module? -----------------------*

**Classes:**
- `WebSecurityConfig` (line 87) `class WebSecurityConfig` - *Resolved security policy for the Flask app.

All fields are read from the environment at import time and frozen so
the policy cannot drift at runtime. Changing a knob requires a restart
— the right call for a tool that mostly runs as a long-lived daemon.*
- `AuthGate` (line 341) `class AuthGate` - *Bearer-token gate applied to sensitive routes.

`required_token` is the single shared secret. `None` disables the gate
(local-trust mode). Comparison is constant-time.*

**Functions:**
- `build_https_url` (line 58) `def build_https_url(public_host, path, query_string)` - *Build a safe HTTPS redirect target from a trusted host and client path.

The client controls ``path``/``query_string``, so they are treated as
hostile (the standing doctrine: never trust user input). We parse the
reconstructed URL and refuse to emit a Location header whose scheme is
not ``https`` or whose host differs from the configured ``public_host``.
On any mismatch we fall back to a bare host root, so a crafted ``//evil``
or ``\evil`` path can never become an open redirect.*

**Methods:**
- `_env_str` (line 137) `def _env_str(name, default)`
- `load_security_config` (line 144) `def load_security_config()` - *Resolve the security policy from env vars.

ESTORIDES_CORS_ORIGINS    comma-separated list, e.g. "https://app.example.com"
ESTORIDES_MAX_BODY_BYTES  int, default 1 MiB
ESTORIDES_HSTS            1 to emit Strict-Transport-Security
ESTORIDES_FORCE_HTTPS     1 to redirect plain http to https (only meaningful behind TLS)
ESTORIDES_CSP             override the default Content-Security-Policy
ESTORIDES_PUBLIC_HOST     public hostname for HTTPS redirect (default localhost:5050)*
- `install_security` (line 169) `def install_security(app, cfg)` - *Wire security middleware into a Flask app.

Idempotent: calling twice is a no-op (we re-attach, but Flask keeps the
last hook, and our hooks are stateless). Returns the resolved config so
the caller can echo it in a startup banner.*
- `_extract_bearer_token` (line 286) `def _extract_bearer_token()` - *Pull the bearer token from header, alt-header, cookie, or query param.

Header order matters: an explicit `Authorization: Bearer` always wins
over a cookie (the cookie is the fallback for the browser UI; the
header is what scripts and curl will use).

Query-param ``?token=`` is a **last resort** for Server-Sent Events
(``EventSource`` cannot set custom headers). It leaks into server
access logs — we accept this limitation because there is no other
transport for streaming endpoints in a browser. The token is never
accepted from query params on POST/PUT/DELETE requests.*
- `make_auth_gate` (line 321) `def make_auth_gate()` - *Build the auth gate from the current environment.

When `ESTORIDES_AUTH_TOKEN` is set, the gate uses that value.
When unset, the system auto-generates a random 64-hex-char token,
stores it in `AUTO_GENERATED_TOKEN` (so the startup banner can
display it), and returns an enabled gate. This guarantees API
abuse protection is always active — no login screen, no explicit
enrolment.*
- `require_auth` (line 388) `def require_auth(view)` - *Decorator: enforce the bearer-token gate on a view.

Behaviour:
  * gate disabled (no env var) → pass-through, no overhead.
  * gate enabled, token missing → 401 with `WWW-Authenticate: Bearer`.
  * gate enabled, token present but wrong → 401 (same shape, constant-
    time compare on the server side).

Use on every endpoint that reads or mutates operator-private data:
cases, run, run/stream/*, discover/*, export, intel/*, transform/*,
osiris/*, graph, status.*
- `install_auth_gate` (line 421) `def install_auth_gate(app, gate)` - *Attach the gate to a Flask app and a module-level slot.

Two consumers read the gate: the `require_auth` decorator (module
slot, so it works even when called outside a request context) and
`auth_meta_for_index()` (so `index.html` can be rendered with the
token embedded for the UI to pick up).*
- `_current_gate` (line 440) `def _current_gate()`
- `auto_generated_token` (line 444) `def auto_generated_token()` - *Return the auto-generated token (None if user set ESTORIDES_AUTH_TOKEN manually).*
- `is_cors_enabled` (line 128) `def is_cors_enabled(self)`
- `is_origin_allowed` (line 132) `def is_origin_allowed(self)` - *CORS is opt-in; this is the runtime check used by the after_request hook.*
- `_security_headers` (line 217) `def _security_headers(resp)`
- `_cors_preflight` (line 250) `def _cors_preflight()`
- `enabled` (line 351) `def enabled(self)`
- `check` (line 354) `def check(self)`
- `auth_meta_for_index` (line 362) `def auth_meta_for_index(self)` - *Token to embed in `index.html` so the UI can auto-authenticate.

Returns `None` when the gate is off (the UI then omits the meta
tag and every call goes through anonymously, which is the
local-trust default).*
- `issue_session_cookie_kwargs` (line 371) `def issue_session_cookie_kwargs(self)` - *Arguments for `set_cookie` to install the session cookie.

`Secure` is set when the request itself is over HTTPS or the operator
requested ESTORIDES_FORCE_HTTPS=1 (in that case we know they're behind
TLS). `SameSite=Lax` keeps the cookie from cross-site POSTs.*
- `wrapper` (line 402) `def wrapper()`
- `_redirect_to_https` (line 203) `def _redirect_to_https()`

#### `__init__.py`
**Path:** `estorides_export/__init__.py`
**File Doc:** *estorides_export*

*No symbols extracted*

#### `encryption.py`
**Path:** `estorides_export/encryption.py`
**File Doc:** *estorides_export.encryption =========================== Optional age-encrypted report export.  The default export path produces STIX / MISP / GraphML as plaintext JSON on disk, which is fine for a local single-user install. For deployments where the report itself is the deliverable (analyst hands a file to a customer, regulator, court), the file should be encrypted at rest with a recipient's public key.  This module wraps the optional `age` encryption tool (`age` is a modern, audited replacement for gpg; the binary is `age` from https://age-encryption.org). If `age` is not on PATH the export falls back to a plaintext file and logs a warning — never to a broken file. This is the "graceful degradation" posture: the operator decides whether encryption is required by whether the recipient public key is set.  Three entry points:  encrypt_file(plaintext_path, recipient_pubkey) -> ciphertext_path Encrypts a single file to a recipient's age public key. The resulting file is `<name>.age` next to the original. Returns the ciphertext path.  export_stix_encrypted(kg, recipient_pubkey, path) -> ciphertext_path export_misp_encrypted(kg, recipient_pubkey, path) -> ciphertext_path Composed: build the bundle, write to a tempfile, encrypt in place, return the .age path.*

**Functions:**
- `_have_age` (line 47) `def _have_age()`
- `encrypt_file` (line 51) `def encrypt_file(plaintext_path, recipient_pubkey)` - *Encrypt `plaintext_path` to `<plaintext_path>.age` for the recipient.

Returns the ciphertext path. Raises RuntimeError if `age` is
missing or the encryption subprocess fails — the orchestrator
catches and falls back to plaintext. Raises ValueError if
`recipient_pubkey` doesn't look like an age public key.

Validation order is: key shape (cheap, no exec) → binary
presence (filesystem stat) → subprocess. A malformed key is
always a programmer error and surfaces as ValueError; a missing
binary is an environment problem and surfaces as RuntimeError.*
- `export_stix_encrypted` (line 100) `def export_stix_encrypted(kg, recipient_pubkey, path)` - *Build the STIX bundle, write to disk, encrypt to <path>.age.

`path` is the plaintext filename; the returned path is the
encrypted artefact next to it. The plaintext is removed once
encryption succeeds so reports/ never accumulates raw intel
bundles — that was the disk-residue problem fixed for issue #8.*
- `export_misp_encrypted` (line 128) `def export_misp_encrypted(kg, recipient_pubkey, path)`

#### `misp.py`
**Path:** `estorides_export/misp.py`
**File Doc:** *estorides_export.misp ===================== Minimal MISP event JSON. Mirrors STIX in spirit but matches the MISP galaxy/cluster schema used by the MISP project.  This is *not* a complete MISP integration — it's enough to import into MISP as an event.*

**Functions:**
- `event_from_graph` (line 36) `def event_from_graph(kg)`
- `_category` (line 65) `def _category(ent_type)`
- `export` (line 79) `def export(kg, path)`

#### `recon_report.py`
**Path:** `estorides_export/recon_report.py`

**Classes:**
- `ReportMetadata` (line 23) `class ReportMetadata`
- `ReportSection` (line 40) `class ReportSection`
- `ReportResult` (line 51) `class ReportResult`

**Methods:**
- `redact_sensitive` (line 61) `def redact_sensitive(text)`
- `build_subdomain_tree` (line 67) `def build_subdomain_tree(subdomains)`
- `build_executive_summary` (line 92) `def build_executive_summary(critical_findings, total_targets, domain, classification)`
- `generate_report` (line 115) `def generate_report(query, target_scoring, metadata)`
- `__post_init__` (line 29) `def __post_init__(self)`
- `to_dict` (line 35) `def to_dict(self)`
- `to_dict` (line 46) `def to_dict(self)`
- `to_dict` (line 57) `def to_dict(self)`

#### `report.py`
**Path:** `estorides_export/report.py`
**File Doc:** *estorides_export.report =======================  Markdown report generation for finished cases.  Design choices -------------- * Pure function. The report builder takes a case dict + a list of entities + (optionally) a previous case id, and returns a Markdown string. No I/O happens here — the caller (CLI, web, scheduled job) decides where the report lands. This keeps the formatter unit- testable in isolation. * Progressive disclosure. The first 30 lines carry the TL;DR so a reviewer can decide in 10 seconds whether the case is worth a deep read. Full IOCs and source roster come after a `---` separator. * Diff support. When `previous_case_id` is supplied, the report includes a "what's new since last run" section that mirrors `CaseStore.diff_entities` — same keys, no double bookkeeping. * No LLM dependency. A separate LLM-narrative step can be wired on top, but the baseline report must work even when no LLM backend is configured (the engine returns a stub analysis in that case).*

**Functions:**
- `_tldr` (line 37) `def _tldr(case, entities, sources_queried, sources_succeeded, diff)` - *Top-of-page executive summary. 6-10 lines max.*
- `_iocs` (line 77) `def _iocs(entities)` - *The sections the next responder (CTI team, SOC) actually pastes
into a ticket. Domains, IPs, emails, hashes, CVEs, crypto addresses.*
- `_diff_section` (line 121) `def _diff_section(diff)` - *The "what's new since last run" block. Empty when no baseline.*
- `_analysis` (line 158) `def _analysis(case)` - *The LLM analysis (or stub) embedded verbatim in a code block.*
- `_meta_footer` (line 179) `def _meta_footer(case, sources_queried, sources_succeeded)`
- `render_markdown_report` (line 196) `def render_markdown_report(case, entities, sources_queried, sources_succeeded, diff)` - *Build a Markdown report for `case`.

Parameters
----------
case
    The case row, as returned by `CaseStore.get_case`. `analysis_json`
    is optional; if present it is embedded verbatim.
entities
    The full entity list. If omitted, only the counts in the case
    row are used (TL;DR still works, IOC section is empty).
diff
    Optional output of `CaseStore.diff_entities(a, b)`. When given,
    a "what's new" section is appended before the analysis.

Returns
-------
str
    The full Markdown document. UTF-8, LF newlines, no trailing
    newline (callers add their own if writing to a file).*

#### `stix.py`
**Path:** `estorides_export/stix.py`
**File Doc:** *estorides_export.stix ===================== STIX 2.1 bundle export. Each unique entity becomes a STIX object (identity, ipv4-addr, domain-name, email-addr, software, vulnerability, url). Co-occurrences become sighting relationships. Sourced by the identity "Estorides".  Spec: https://docs.oasis-open.org/cti/stix/v2.1/cs01/stix-v2.1-cs01.html*

**Functions:**
- `_id` (line 28) `def _id(stix_type)`
- `_now` (line 32) `def _now()`
- `bundle_from_graph` (line 55) `def bundle_from_graph(kg)`
- `export` (line 145) `def export(kg, path)`

#### `__init__.py`
**Path:** `estorides_llm/__init__.py`
**File Doc:** *estorides_llm*

*No symbols extracted*

#### `intelligence_prompts.py`
**Path:** `estorides_llm/intelligence_prompts.py`
**File Doc:** *estorides_llm.intelligence_prompts ================================== System prompt and context-formatting helpers for the LLM analyst.  The system prompt is the single most important piece of text in the whole stack. It is what turns "a wall of OSINT JSON" into "a Palantir-grade intelligence brief". The previous version was a six-line paragraph that read like a CV; this one is structured the way senior analysts actually brief:  * Persona is set first and never compromised * Output format is prescribed (BLUF, confidence-graded findings, recommended actions) * Hard constraints are explicit (no fabrication, citation required, "I don't know" is acceptable)  Three prompt flavours are exposed:  * SYSTEM_PROMPT — default, used by the orchestrator * BLUF_PROMPT   — strict 1-paragraph + confidence-graded findings * TACTICAL_PROMPT — operator-grade with Course of Action (COA) recommendations  The chosen prompt is selected via the `prompt_style` argument to `LLMManager.generate()`. Backends that ignore the prompt style are not affected; the new behaviour is opt-in for callers that care.*

**Functions:**
- `format_context` (line 97) `def format_context(sources)` - *Render a list of observation dicts into a context block for the LLM.

The previous implementation truncated at 3500 chars but did not
preserve source ordering, so a re-run produced a different prompt
and a different answer. This version sorts by source name (stable
order) and truncates per source so a single 100KB crt.sh response
cannot blow past the model's context window.

A hard ``max_total_chars`` cap bounds the WHOLE block. Without it a
wide run (dozens of sources) could produce a prompt that dwarfs the
model's context window, and the model would only see the first blocks
— silently dropping the data it actually needs to answer. The cap is
applied in source order so the assessment never goes blind on a large
collection.

If the orchestrator has stamped an ontology verdict on an
observation (the `ontology` key), the formatted block carries a
one-line "SANCTIONED — OFAC SDN match on <fields>" warning that
the LLM is required to surface in its assessment.*

#### `manager.py`
**Path:** `estorides_llm/manager.py`
**File Doc:** *estorides_llm.manager ===================== Multi-backend LLM router with pluggable backends.  A `LLMBackend` is a small Protocol that knows how to talk to one provider (ollama, openai, anthropic, openrouter, …). Backends register themselves in `BACKENDS` and the manager walks the list in priority order, returning the first successful response.  This is the registry pattern in place of the old if/elif chain that hard-coded a method per backend. Adding a new provider is now: write a class, `@register("name")` it, done. No edits to the manager.  System prompt lives in `intelligence_prompts.py` so multiple prompts (BLUF, tactical, standard) can be selected per call without forking the manager.*

**Classes:**
- `LLMBackend` (line 60) `class LLMBackend(Protocol)` - *Minimal contract for an LLM backend.

Implementations MUST be total: raise on failure (the manager
catches and moves on) or return ("", "") to signal "I can't
answer, try the next backend".*
- `OllamaBackend` (line 120) `class OllamaBackend`
- `_OpenAICompatibleBackend` (line 266) `class _OpenAICompatibleBackend` - *Shared implementation for OpenAI-shaped APIs (openai, openrouter, …).

Subclasses set `name`, `env_key`, and `base_url`.*
- `OpenAIBackend` (line 302) `class OpenAIBackend(_OpenAICompatibleBackend)`
- `OpenRouterBackend` (line 309) `class OpenRouterBackend(_OpenAICompatibleBackend)`
- `AnthropicBackend` (line 316) `class AnthropicBackend`
- `LLMManager` (line 356) `class LLMManager`

**Methods:**
- `register` (line 97) `def register(name)` - *Decorator: register a backend under `name`.

Accepts both an instance and a class. If a class is given, the
decorator instantiates it with no arguments — which is the
common case for stateless backends that hold no per-instance
state. The class must therefore have a no-arg constructor.*
- `__call__` (line 69) `def __call__(self, prompt, context, max_tokens, temperature, request_timeout)` - *Return (content, model_id). Empty content means "skip me".*
- `stream_generate` (line 80) `def stream_generate(self, prompt, context, model, temperature, request_timeout)` - *Optional: stream (kind, text) chunks. Base backends may skip.*
- `deco` (line 105) `def deco(backend_or_cls)`
- `get_status` (line 124) `def get_status()` - *Return available ollama models and reachability status.*
- `_resolve_model` (line 134) `def _resolve_model(self, request_timeout)` - *Pick a model ollama actually has pulled.

If ESTORIDES_OLLAMA_MODEL is set via env, use that (verify it's
pulled). Otherwise auto-detect from /api/tags and use the first
available model.*
- `stream_generate` (line 188) `def stream_generate(self, prompt, context, model, temperature, request_timeout)` - *Stream an ollama response as (kind, text) chunks.

kind ∈ {"thinking", "content"} so the UI can render the model's
reasoning preamble into a collapsible "thinking" panel and stream
the final answer below it. Yields dicts, never raises; a hard
``request_timeout`` guards the whole stream.*
- `__call__` (line 227) `def __call__(self, prompt, context, max_tokens, temperature, request_timeout)`
- `__call__` (line 274) `def __call__(self, prompt, context, max_tokens, temperature, request_timeout)`
- `__call__` (line 319) `def __call__(self, prompt, context, max_tokens, temperature, request_timeout)`
- `__init__` (line 357) `def __init__(self)`
- `generate` (line 373) `def generate(self, prompt)` - *Try each backend in priority order; return the first that succeeds.

`request_timeout` caps every backend's HTTP call so a slow
local model cannot keep a worker thread alive past the
orchestrator's deadline. Returns a dict with keys:
backend, model, content, error.*
- `get_ollama_status` (line 414) `def get_ollama_status(self)` - *Return ollama reachability and available models.*
- `stream` (line 422) `def stream(self, prompt)` - *Stream an analysis from a specific ollama model.

Yields ``{"kind": "thinking"|"content", "text": …}`` chunks. The
model name is treated as hostile: it must match an actual ollama
model, else we raise before touching the network. If no model is
given, we fall back to the auto-detected fast default.*
- `_stub_response` (line 459) `def _stub_response(self, prompt, context)`

#### `estorides_web.py`
**Path:** `estorides_web.py`
**File Doc:** *estorides.web ============= Flask app providing: * 2D map     (Leaflet) * knowledge graph (D3.js force-directed) * timeline * source results panel * multi-LLM analysis*

**Classes:**
- `_RunStreamJob` (line 149) `class _RunStreamJob` - *A live deep-run cross-search whose events feed an SSE stream.

Wraps a `BufferedEventSink` (the engine writes to it) and a cooperative
stop flag the UI can set. Status and terminal state are read straight
off the sink so there is one source of truth.*

**Functions:**
- `_sse_response` (line 78) `def _sse_response(gen)` - *Wrap an event generator in an SSE `Response` with the shared headers.*
- `_provides` (line 83) `def _provides(service, message)` - *Guard a route on an optional service being wired.

Replaces the ~30 hand-written `if x is None: return ..., 503` blocks
scattered through `create_app`. `service` is captured at decoration
time (it is assigned once before the routes are declared).*
- `_client_ip` (line 102) `def _client_ip()` - *Best-effort client IP extraction.

Trusts X-Forwarded-For only when behind a known proxy
(ESTORIDES_TRUST_PROXY=1). Without that, falls back to
`request.remote_addr`. This avoids the classic
"set X-Forwarded-For to bypass rate limits" mistake on a
directly-exposed deployment.*
- `_arg_int` (line 119) `def _arg_int(name, default)` - *Read an int query-string arg, falling back to `default` on parse error.

Guards every endpoint that previously did `int(request.args.get(...))`
directly, where a non-numeric value raised ValueError and surfaced as
an unhandled 500 to the client.*
- `_send_and_cleanup` (line 135) `def _send_and_cleanup(p, tmpdir)` - *Send `p` as an attachment, then nuke `tmpdir` regardless of outcome.

Used by /api/export/<fmt> so reports/ doesn't accumulate a copy of
every exported bundle — see issue #43. The `finally` runs even when
the client disconnects mid-stream, which is the realistic failure
case for the exhaustion attack.*

**Methods:**
- `_new_stream_job_id` (line 187) `def _new_stream_job_id()` - *Timestamp-prefixed id so jobs sort chronologically.*
- `_rate_limit_decorator` (line 192) `def _rate_limit_decorator()` - *Decorator: enforce per-IP rate limit, write an audit row either way.

Catches the rate-limit denial BEFORE doing real work, so a flood
can't tie up the orchestrator. Audit row written for both allow
and deny so the trail is complete.*
- `_graph_rag_block` (line 236) `def _graph_rag_block(query, budget_tokens)` - *GraphRAG context for the local-AI prompt (spec/graph_rag_search.md).

Fail-soft: any problem (no graph yet, bad data) yields "" so the
analysis prompt goes out unchanged. CWE-209: detail stays in logs.*
- `create_app` (line 258) `def create_app()`
- `_serve_loop` (line 1792) `def _serve_loop()`
- `_shape_for_ui` (line 1805) `def _shape_for_ui(result)` - *Trim raw responses for the UI and reformat observations.*
- `deco` (line 90) `def deco(view)`
- `__init__` (line 157) `def __init__(self, job_id, query, query_type, case_id)`
- `stop` (line 166) `def stop(self)`
- `should_stop` (line 169) `def should_stop(self)`
- `status` (line 173) `def status(self)`
- `done` (line 177) `def done(self)`
- `deco` (line 199) `def deco(view)`
- `healthz` (line 294) `def healthz()` - *Return liveness without auth for container probes.*
- `readyz` (line 305) `def readyz()` - *Return readiness based on source registry state.*
- `metrics` (line 319) `def metrics()` - *Return Prometheus text counters for this process.*
- `openapi_doc` (line 326) `def openapi_doc()` - *Return the generated OpenAPI document for this app.*
- `index` (line 331) `def index()`
- `api_status` (line 352) `def api_status()`
- `api_ollama_status` (line 358) `def api_ollama_status()`
- `api_run` (line 364) `def api_run()`
- `api_lan_scope` (line 428) `def api_lan_scope()`
- `api_lan_scan` (line 436) `def api_lan_scan()`
- `api_graph` (line 474) `def api_graph()`
- `api_feeds` (line 577) `def api_feeds()` - *Return real-time feed points (quakes, fires, news) for the map.

Optional query string:
  bbox=min_lon,min_lat,max_lon,max_lat — drop points outside.
  no_cache=1 — bypass the on-disk cache.*
- `api_export` (line 608) `def api_export(fmt)`
- `api_cases_list` (line 686) `def api_cases_list()`
- `api_cases_get` (line 699) `def api_cases_get(case_id)`
- `api_cases_delete` (line 713) `def api_cases_delete(case_id)`
- `api_cases_save` (line 721) `def api_cases_save(case_id)` - *Bookmark a case from the UI.

Sets a `notes` prefix so the case is easy to spot in the cases
list, then echoes the updated case back. The store is
append-only for observations, but `notes` is a free-text column
we can overwrite. This is the "I want to come back to this"
gesture: in v1 the only durable artefact was the case id; in
v1.3 we want the user to be able to tag their wins.*
- `api_cases_diff` (line 744) `def api_cases_diff()` - *Symmetric diff between two cases by entity (type, value).

Query string: ?a=<case_id>&b=<case_id>
Returns the entities present in B but not in A ("added"), the
inverse ("removed"), and the per-type breakdown. The UI uses
this to show "what's new since last run" without a re-query.*
- `api_intel_resolve` (line 769) `def api_intel_resolve()` - *Cross-feed entity resolution (Osiris-style /resolve).

Examples:
  GET /api/intel/resolve?type=ip&id=1.1.1.1
  GET /api/intel/resolve?type=person&id=Tim%20Cook
  GET /api/intel/resolve?type=cve&id=CVE-2024-3094*
- `api_intel_graph` (line 808) `def api_intel_graph()` - *Cypher query against the Kùzu persistent graph.

Examples:
  GET /api/intel/graph?q=MATCH%20(n%3AEnt)%20RETURN%20n.id%20LIMIT%2010*
- `api_intel_stats` (line 847) `def api_intel_stats()` - *Stats for both the case store and the Kùzu graph.*
- `api_fusion_stats` (line 868) `def api_fusion_stats()` - *One-glance dashboard of the fused, cross-run fact base.*
- `api_fusion_sources` (line 876) `def api_fusion_sources()` - *The YAML source catalogue with accumulated fetch/ok counters.*
- `api_fusion_entities` (line 885) `def api_fusion_entities()` - *Search fused entities.

Example: GET /api/fusion/entities?q=google&type=domain&min_sources=2
``min_sources`` is the fusion-native filter: only entities that at
least N distinct feeds corroborate.*
- `api_fusion_entity` (line 906) `def api_fusion_entity(eid)` - *Full fused view of one entity: provenance, properties, edges.

``min_sources`` (default 2) also returns the corroborated properties:
attributes that independent feeds agree on.*
- `api_fusion_analytics_entity_timeline` (line 924) `def api_fusion_analytics_entity_timeline(eid)`
- `api_fusion_analytics_entity_summary` (line 934) `def api_fusion_analytics_entity_summary(eid)`
- `api_fusion_analytics_source_stats` (line 944) `def api_fusion_analytics_source_stats(source_name)`
- `api_fusion_analytics_consensus` (line 954) `def api_fusion_analytics_consensus(eid)`
- `api_fusion_analytics_top_changed` (line 964) `def api_fusion_analytics_top_changed()`
- `admin_sources` (line 974) `def admin_sources()` - *Render the YAML source manager page.*
- `api_sources_yaml_list` (line 991) `def api_sources_yaml_list()` - *Return every YAML source with full configuration.*
- `api_sources_yaml_create` (line 1017) `def api_sources_yaml_create()` - *Create a new YAML source.*
- `api_sources_yaml_update` (line 1038) `def api_sources_yaml_update(name)` - *Update/replace a YAML source.*
- `api_sources_yaml_delete` (line 1057) `def api_sources_yaml_delete(name)` - *Delete a YAML source.*
- `api_fusion_analytics_corroboration_matrix` (line 1074) `def api_fusion_analytics_corroboration_matrix()`
- `api_socmint_resolve` (line 1088) `def api_socmint_resolve()` - *Resolve a username across known social media platforms.

Example: GET /api/socmint/resolve?username=torvalds
         GET /api/socmint/resolve?username=torvalds&platforms=github,keybase

Returns a SocialMediaProfile with profile URLs for every platform.*
- `api_socmint_platforms` (line 1110) `def api_socmint_platforms()` - *Return the list of all known social media platforms.*
- `api_socmint_discover` (line 1118) `def api_socmint_discover()` - *Extract social media profile URLs from a text blob.

Body: {"text": "Follow me on Twitter: https://x.com/johndoe"}*
- `api_watch_list` (line 1161) `def api_watch_list()` - *List all watch targets.*
- `api_watch_create` (line 1170) `def api_watch_create()` - *Create a new watch target.

Body: {"query": "example.com", "type": "domain", "interval": 1440, "channels": ["slack"], "notes": "..."}*
- `api_watch_get` (line 1204) `def api_watch_get(watch_id)`
- `api_watch_delete` (line 1216) `def api_watch_delete(watch_id)`
- `api_watch_enable` (line 1227) `def api_watch_enable(watch_id)`
- `api_watch_disable` (line 1240) `def api_watch_disable(watch_id)`
- `api_watch_history` (line 1252) `def api_watch_history(watch_id)`
- `api_alerts_channels` (line 1262) `def api_alerts_channels()` - *List configured alert channels and their status.*
- `api_alerts_test` (line 1270) `def api_alerts_test()` - *Send a test alert to a channel.

Body: {"channel": "slack"}*
- `api_scheduler_status` (line 1286) `def api_scheduler_status()`
- `api_transforms` (line 1304) `def api_transforms()` - *List the transforms applicable to an entity type.

Example: GET /api/transforms?type=ip*
- `api_transform_run` (line 1318) `def api_transform_run()` - *Run one transform and return nodes/links for graph merge.

Body: {"transform_id": "...", "type": "ip", "value": "1.2.3.4"}*
- `api_transform_stream` (line 1340) `def api_transform_stream()` - *Stream one transform as SSE `node`/`link` events plus `done`.

Query: ?transform_id=...&type=...&value=... The D3 graph merges
events progressively instead of waiting for the full payload.*
- `api_osiris_bgp` (line 1379) `def api_osiris_bgp()`
- `api_osiris_mac` (line 1393) `def api_osiris_mac()`
- `api_osiris_phone` (line 1407) `def api_osiris_phone()`
- `api_osiris_github` (line 1421) `def api_osiris_github()`
- `api_osiris_leaks` (line 1435) `def api_osiris_leaks()`
- `api_osiris_kev` (line 1449) `def api_osiris_kev()`
- `api_osiris_malware` (line 1458) `def api_osiris_malware()`
- `api_osiris_threats` (line 1463) `def api_osiris_threats()`
- `api_discover_start` (line 1476) `def api_discover_start()`
- `api_discover_jobs` (line 1529) `def api_discover_jobs()`
- `api_discover_stop` (line 1535) `def api_discover_stop()`
- `api_discover_stream` (line 1547) `def api_discover_stream()` - *Server-Sent Events for a discoverer job.

The browser opens `EventSource('/api/discover/stream?job_id=...')`
and we keep the connection open, pushing one event per
JSON line as the background worker discovers things. The
stream closes when the job finishes (status=done|error|stopped).*
- `api_run_stream_start` (line 1597) `def api_run_stream_start()`
- `api_run_stream_stop` (line 1669) `def api_run_stream_stop()`
- `api_run_stream` (line 1681) `def api_run_stream()`
- `api_analyze_stream` (line 1728) `def api_analyze_stream()`
- `wrapper` (line 92) `def wrapper()`
- `wrapper` (line 201) `def wrapper()`
- `_gen` (line 1356) `def _gen()`
- `gen` (line 1560) `def gen()`
- `_drive` (line 1632) `def _drive()`
- `gen` (line 1687) `def gen()`
- `_run` (line 1741) `def _run()`
- `gen` (line 1763) `def gen()`
- `_watch_runner` (line 1137) `def _watch_runner(swatch)`
- `_err` (line 1351) `def _err()`

#### `estorides_web_tools.py`
**Path:** `estorides_web_tools.py`
**File Doc:** *estorides_web_tools =================== Tools vertical slice extracted from the ``create_app`` God factory.  Owns the install-state registry and the four ``/api/tools*`` routes with identical URLs, auth, rate-limit events and behaviours. The factory registers :data:`tools_bp`; no tools view logic stays inline.  Import cycle note: this module is imported lazily inside ``create_app`` (call time, after ``estorides_web`` is fully loaded), so top-level imports of the factory helpers are safe.*

**Functions:**
- `api_tools_list` (line 49) `def api_tools_list()`
- `api_tools_doctor` (line 68) `def api_tools_doctor()`
- `api_tool_install` (line 75) `def api_tool_install(name)`
- `api_tool_install_status` (line 120) `def api_tool_install_status(name)`
- `_worker` (line 91) `def _worker()`

#### `conftest.py`
**Path:** `tests/conftest.py`
**File Doc:** *Pytest configuration and shared fixtures for the estorides test suite.*

*No symbols extracted*

#### `conftest.py`
**Path:** `tests/properties/conftest.py`
**File Doc:** *Directory guard for property-based (hypothesis) tests.  ``hypothesis`` is a dev-only extra (``pip install -e ".[dev]"``; CI installs it). Generic runners such as ``deploynator release`` (``python -m pytest tests/ -q`` with the system interpreter) only have runtime deps, so a hard ``from hypothesis import ...`` at module top turns into 13 collection *errors* that abort the whole deploy gate.  When the extra is missing (or broken: e.g. a leftover directory without ``__init__`` that imports as an empty namespace package) the property files are excluded from collection (``collect_ignore_glob`` — the pytest-native mechanism for this); when it is installed (CI / ``.[dev]``) this file is a no-op and the full fuzz matrix runs. ``pytest.importorskip`` cannot be used here: raised inside a conftest it surfaces as a collection *error*, not a skip.*

*No symbols extracted*

#### `test_change_detection_properties.py`
**Path:** `tests/properties/test_change_detection_properties.py`
**File Doc:** *Property-based invariants for estorides_core.change_detection.  Hypothesis fuzzing: random snapshots, must always satisfy the boundedness invariants declared in spec S12.  Run from the project root::  .venv/bin/pytest tests/properties/test_change_detection_properties.py -v*

**Functions:**
- `test_scores_always_bounded` (line 69) `def test_scores_always_bounded(before, after)`
- `test_max_changes_respected` (line 77) `def test_max_changes_respected(before, after)`
- `test_id_is_16_char_hex` (line 86) `def test_id_is_16_char_hex(before, after)`
- `test_idempotent` (line 95) `def test_idempotent(before, after)`
- `test_first_run_reports_all_as_new` (line 105) `def test_first_run_reports_all_as_new(after)`
- `test_after_none_returns_empty` (line 113) `def test_after_none_returns_empty(before)`
- `test_before_vs_no_after_empty` (line 121) `def test_before_vs_no_after_empty(entities)`
- `test_summary_consistency` (line 132) `def test_summary_consistency(before, after)` - *Whatever the input, the summary fields must agree with the
``changes`` list: ``total == len(changes)``, ``by_kind`` counts
each kind exactly, ``score_max/mean`` are derived from the actual
changes, and ``entities_compared == len(before.entities)`` (or 0 if
``before`` is None).*

#### `test_csp_safe_styles_properties.py`
**Path:** `tests/properties/test_csp_safe_styles_properties.py`
**File Doc:** *Property-based fuzz for `csp_safe_styles`.  Defends against a future contributor who: * re-introduces a `style="..."` template literal in the JS, * adds a new template variable to index.html that could be injection vector for an inline style attribute, * "fixes" the CSP by adding `'unsafe-inline'` and silently regresses the security property.  Each property is run with at least 1000 examples (doctrine §6).*

**Functions:**
- `test_js_never_gains_a_style_attribute_in_template_literal` (line 58) `def test_js_never_gains_a_style_attribute_in_template_literal(insertion)` - *Hypothetical: a future patch appends the given string somewhere
in the JS. The codebase should still be free of `style="…"` in any
backtick-delimited template literal.

Strategy: simulate the patch as the insertion landing in any random
position of the file, then re-check the invariant. With 1000
random `insertion` x random offset combinations, the test exercises
the structural property: there is no `style="…"` substring inside
any backtick literal in the current file.*
- `test_template_never_gains_a_style_attribute` (line 105) `def test_template_never_gains_a_style_attribute(insertion)` - *Hypothetical: a future patch adds the given string somewhere in
the template. The contract is that `style="…"` does not exist in
any static markup of `templates/index.html`.

Jinja expressions `{{ … }}` and `{% … %}` are stripped before
scanning so we test the static part of the document.*
- `test_csp_style_src_never_gains_unsafe_inline` (line 137) `def test_csp_style_src_never_gains_unsafe_inline(bad)` - *Hypothetical: a future patch sets `style-src` to
`'self' 'unsafe-hashes' https://unpkg.com 'unsafe-inline'`. The
property-based test asserts the current CSP doesn't have
`'unsafe-inline'` in style-src, and that any string containing
`bad` in that position is rejected.

We assert the current state, not a future mutation. With 1000
random `bad` values, the structural check fires: the style-src
list never contains the unsafe keyword.*

#### `test_graph_bundle_properties.py`
**Path:** `tests/properties/test_graph_bundle_properties.py`
**File Doc:** *Property-based invariants for estorides_core.graph_bundle.  Fuzzing con 1000 ejemplos por propiedad (doctrina: input hostil). P1 totalidad (nunca crash fuera de TypeError + salida consistente), P2 determinismo byte-identico, P3 vacio, P4 settings acotados.*

**Functions:**
- `force_strategy` (line 32) `def force_strategy(draw)`
- `_assert_consistent` (line 82) `def _assert_consistent(out, raw)`
- `test_p1_total_and_consistent` (line 104) `def test_p1_total_and_consistent(payload)` - *P1 — jamas crash fuera de TypeError; salida consistente y JSON-safe.*
- `test_p2_deterministic` (line 115) `def test_p2_deterministic(payload)` - *P2 — dos pasadas byte-identicas (o ambas TypeError).*
- `test_p3_empty_never_raises` (line 131) `def test_p3_empty_never_raises(payloads)` - *P3 — payload vacio siempre devuelve vacio sin raise.*
- `test_p4_settings_bounded` (line 141) `def test_p4_settings_bounded(data)` - *P4 — settings con claves fijas y beta/perspective acotados.*

#### `test_hypothesis_engine_properties.py`
**Path:** `tests/properties/test_hypothesis_engine_properties.py`
**File Doc:** *Property-based invariants for estorides_core.hypothesis_engine.  Hypothesis fuzzing: random observations + entities, must always satisfy the boundedness invariants declared in spec S10.  Run from the project root::  .venv/bin/pytest tests/properties/test_hypothesis_engine_properties.py -v*

**Functions:**
- `test_scores_always_bounded` (line 54) `def test_scores_always_bounded(observations, entities)`
- `test_claim_length_under_cap` (line 66) `def test_claim_length_under_cap(observations, entities)`
- `test_reasoning_length_under_cap` (line 77) `def test_reasoning_length_under_cap(observations, entities)`
- `test_sources_sorted_unique` (line 88) `def test_sources_sorted_unique(observations, entities)`
- `test_id_is_deterministic_hex` (line 99) `def test_id_is_deterministic_hex(observations, entities)`
- `test_idempotent` (line 112) `def test_idempotent(observations, entities)`
- `test_max_hypotheses_caps_output` (line 123) `def test_max_hypotheses_caps_output(observations, entities)`
- `test_min_score_filters` (line 134) `def test_min_score_filters(observations, entities)`
- `test_hostile_observation_does_not_crash` (line 147) `def test_hostile_observation_does_not_crash(observations, entities)`

#### `test_observation_models_properties.py`
**Path:** `tests/properties/test_observation_models_properties.py`
**File Doc:** *Property-based invariants for estorides_core.observation_models.  Each ``@given`` run exercises at least 1000 random examples by default (hypothesis derives the count from the example budget). The invariants are the executable translation of BDD scenario O9 in spec/observation_models.md.*

**Functions:**
- `meta_strategy` (line 45) `def meta_strategy(draw)`
- `obs_strategy` (line 60) `def obs_strategy(draw)`
- `entity_strategy` (line 76) `def entity_strategy(draw)`
- `test_observation_round_trip_stability` (line 90) `def test_observation_round_trip_stability(payload)`
- `test_observation_bounded_fields` (line 103) `def test_observation_bounded_fields(payload)`
- `test_entity_round_trip_and_bounds` (line 115) `def test_entity_round_trip_and_bounds(payload)`
- `test_meta_never_echoes_unbounded_url` (line 128) `def test_meta_never_echoes_unbounded_url(metas)`

#### `test_parsers_properties.py`
**Path:** `tests/properties/test_parsers_properties.py`
**File Doc:** *Property-based fuzzing for the parser totality contract (doctrine §6).  Every registered parser must be total: any JSON-shaped input returns a value instead of raising. We fuzz all 75 parsers with arbitrary nested primitives, lists and dicts (1000 examples per property).*

**Functions:**
- `test_all_parsers_are_total` (line 35) `def test_all_parsers_are_total(payload)`

#### `test_recon_fusion_properties.py`
**Path:** `tests/properties/test_recon_fusion_properties.py`
**File Doc:** *Property-based fuzzing for recon_fusion module (doctrine section 6).  Verifies invariants hold for arbitrary valid inputs. Minimum 1000 examples per property.*

**Classes:**
- `TestPropertyScoreBounds` (line 48) `class TestPropertyScoreBounds` - *P1 -- Scores are always in [0, 1].*
- `TestPropertyTotalCounts` (line 68) `class TestPropertyTotalCounts` - *P2 -- total_observations and total_entities match input.*
- `TestPropertyTierSumMatches` (line 93) `class TestPropertyTierSumMatches` - *P3 -- tier_summary counts match actual tier list lengths.*
- `TestPropertyDeterminism` (line 114) `class TestPropertyDeterminism` - *P4 -- Same input always produces same output (time-independent fields excluded).*
- `TestPropertyNoDuplicates` (line 135) `class TestPropertyNoDuplicates` - *P5 -- No duplicate canonical_id within the same tier.*
- `TestPropertyTierKeysOrder` (line 155) `class TestPropertyTierKeysOrder` - *P6 -- result.tiers dict preserves canonical tier key order.*
- `TestPropertyEmptyQueryRejected` (line 176) `class TestPropertyEmptyQueryRejected` - *P7 -- Empty query always raises ValueError.*
- `TestPropertySafeWithBadInputs` (line 196) `class TestPropertySafeWithBadInputs` - *P8 -- None observations/entities never crash.*

**Methods:**
- `test_all_scores_in_unit_interval` (line 58) `def test_all_scores_in_unit_interval(self, query, query_type, observations, entities)`
- `test_counts_match_input` (line 78) `def test_counts_match_input(self, query, query_type, n_obs, n_ents)`
- `test_tier_summary_matches` (line 103) `def test_tier_summary_matches(self, query, query_type, observations, entities)`
- `test_deterministic_output` (line 124) `def test_deterministic_output(self, query, query_type, observations, entities)`
- `test_no_duplicate_ids_in_tier` (line 145) `def test_no_duplicate_ids_in_tier(self, query, query_type, observations, entities)`
- `test_tier_keys_in_canonical_order` (line 165) `def test_tier_keys_in_canonical_order(self, query, query_type, observations, entities)`
- `test_empty_query_raises` (line 185) `def test_empty_query_raises(self, query_type, observations, entities)`
- `test_none_inputs_safe` (line 201) `def test_none_inputs_safe(self, query, query_type)`
- `test_entities_none_is_safe` (line 214) `def test_entities_none_is_safe(self, query, query_type, observations)`

#### `test_reliability_scoring_properties.py`
**Path:** `tests/properties/test_reliability_scoring_properties.py`
**File Doc:** *Property-based invariants for estorides_core.reliability_scoring.  Hypothesis replaces libFuzzer/AFL for Python. These tests run >= 1000 random examples per property and must never crash, raise (except ``InvalidArgument`` from input validation), or violate the invariants.  Run from the project root::  .venv/bin/pytest tests/properties/test_reliability_scoring_properties.py -v*

**Functions:**
- `test_score_always_bounded` (line 56) `def test_score_always_bounded(reliability, credibility, corroboration, age, base, half_life)`
- `test_corroboration_weight_in_unit_interval` (line 73) `def test_corroboration_weight_in_unit_interval(n)`
- `test_freshness_monotone_in_age` (line 87) `def test_freshness_monotone_in_age(age1, age2)`
- `test_reliability_from_name_never_raises` (line 110) `def test_reliability_from_name_never_raises(name)`
- `test_merge_confidence_bounded` (line 126) `def test_merge_confidence_bounded(existing, new_obs, new_rel, new_cred, cor, age)`
- `test_reliability_weight_set_is_curated` (line 141) `def test_reliability_weight_set_is_curated()`
- `test_credibility_weight_set_is_curated` (line 146) `def test_credibility_weight_set_is_curated()`
- `test_corroboration_is_monotone_in_count` (line 159) `def test_corroboration_is_monotone_in_count(n1, n2)`
- `test_higher_reliability_dominates` (line 184) `def test_higher_reliability_dominates(rel1, rel2)`
- `test_source_type_from_name_never_raises` (line 202) `def test_source_type_from_name_never_raises(name)`
- `test_source_type_weight_always_curated` (line 219) `def test_source_type_weight_always_curated(reliability, credibility, source_type, corroboration, age, base, half_life)`
- `test_source_type_weight_set_is_curated` (line 241) `def test_source_type_weight_set_is_curated()`

#### `test_search_telemetry_properties.py`
**Path:** `tests/properties/test_search_telemetry_properties.py`
**File Doc:** *Property-based fuzzing for `search_telemetry` (spec S12 + predicate laws).  Each property runs >= 1000 random examples (doctrine section 6). The progress computation must be total over the integers (never raise on numeric input) and keep its bounds invariant; the brand/emoji predicates must be total over arbitrary text and never raise.*

**Functions:**
- `test_progress_invariants_hold` (line 32) `def test_progress_invariants_hold(completed, total, phase_key)`
- `test_progress_rejects_unknown_phase` (line 46) `def test_progress_rejects_unknown_phase(phase_key)`
- `test_brand_predicate_is_total` (line 56) `def test_brand_predicate_is_total(text)`
- `test_emoji_predicate_is_total` (line 65) `def test_emoji_predicate_is_total(text)`
- `test_percent_encoded_emoji_predicate_is_total` (line 74) `def test_percent_encoded_emoji_predicate_is_total(text)`
- `test_brand_predicate_flags_embedded_brand` (line 84) `def test_brand_predicate_flags_embedded_brand(prefix, suffix)`

#### `test_source_health_monitoring_properties.py`
**Path:** `tests/properties/test_source_health_monitoring_properties.py`
**File Doc:** *Property-based invariants for estorides_core.source_health_monitoring.  Hypothesis replaces libFuzzer/AFL for Python. These tests run >= 1000 random examples per property.*

**Functions:**
- `_valid_input` (line 21) `def _valid_input(fetch, ok, latency, last_seen, now)` - *Build a valid SourceHealthInput, clamping ok <= fetch.*
- `test_health_score_always_bounded` (line 49) `def test_health_score_always_bounded(fetch, ok, latency, last_seen, now)`
- `test_status_always_valid_enum` (line 63) `def test_status_always_valid_enum(fetch, ok, latency, last_seen, now)`
- `test_success_rate_bounds` (line 78) `def test_success_rate_bounds(fetch, ok, latency, last_seen, now)`
- `test_unknown_when_below_min_fetches` (line 89) `def test_unknown_when_below_min_fetches(fetch, config_min)`
- `valid_health_inputs` (line 97) `def valid_health_inputs(draw)`
- `test_dashboard_summary_counts_match` (line 112) `def test_dashboard_summary_counts_match(records)` - *Dashboard summary counts must sum to total.*

#### `test_system_app_sources_properties.py`
**Path:** `tests/properties/test_system_app_sources_properties.py`
**File Doc:** *Property-based invariants for estorides_core.system_app_sources.  Hypothesis replaces libFuzzer/AFL. >= 1000 random examples per property. Run from the project root::  .venv/bin/pytest tests/properties/test_system_app_sources_properties.py -v*

**Functions:**
- `test_tool_parsers_never_raise` (line 37) `def test_tool_parsers_never_raise(parser_name, blob)`
- `test_render_args_safe_inputs_no_metachars` (line 55) `def test_render_args_safe_inputs_no_metachars(args, query, outdir)`
- `test_render_args_substitution_is_verbatim` (line 70) `def test_render_args_substitution_is_verbatim(query, outdir)`
- `test_read_capped_respects_limit` (line 83) `def test_read_capped_respects_limit(blob, cap)`
- `test_parse_tool_output_never_raises` (line 110) `def test_parse_tool_output_never_raises(parser_name, data)`
- `test_adversarial_query_rejected_at_runner_boundary` (line 124) `def test_adversarial_query_rejected_at_runner_boundary(prefix, bad, suffix)`

#### `test_target_management_properties.py`
**Path:** `tests/properties/test_target_management_properties.py`

**Functions:**
- `test_p1_add_target_never_raises` (line 21) `def test_p1_add_target_never_raises(etype, value)`
- `test_p2_validated_id_is_deterministic` (line 31) `def test_p2_validated_id_is_deterministic(etype, value)`
- `test_p3_make_target_id_stable_under_case` (line 40) `def test_p3_make_target_id_stable_under_case(etype, value)`
- `test_p4_valid_domains_validate` (line 56) `def test_p4_valid_domains_validate(d)`
- `test_p5_valid_ipv4_validate` (line 68) `def test_p5_valid_ipv4_validate(ip)`
- `test_p6_valid_emails_validate` (line 77) `def test_p6_valid_emails_validate(email)`
- `test_p7_auto_detect_never_fails` (line 83) `def test_p7_auto_detect_never_fails(value)`
- `test_p8_validate_target_never_raises` (line 89) `def test_p8_validate_target_never_raises(value)`
- `test_p9_batch_import_idempotent` (line 105) `def test_p9_batch_import_idempotent(targets)`
- `test_p10_batch_import_never_raises` (line 116) `def test_p10_batch_import_never_raises(text)`

#### `test_tool_runner_properties.py`
**Path:** `tests/properties/test_tool_runner_properties.py`
**File Doc:** *Property-based invariants for estorides_core.tool_runner.  Hypothesis replaces libFuzzer/AFL for Python. These tests run >= 1000 random examples per property and must never crash, raise (except ``InvalidArgument`` from input validation), or violate the invariants.  Run from the project root::  .venv/bin/pytest tests/properties/test_tool_runner_properties.py -v*

**Functions:**
- `test_check_injection_safe_strings_silent` (line 35) `def test_check_injection_safe_strings_silent(args)`
- `test_check_injection_detects_all_metacharacters` (line 46) `def test_check_injection_detects_all_metacharacters(prefix, bad, suffix)`
- `test_run_tool_never_raises` (line 63) `def test_run_tool_never_raises(target)`

#### `test_active_recon.py`
**Path:** `tests/test_active_recon.py`
**File Doc:** *ATDD + BDD tests for estorides_core.active_recon.  Implements the Given-When-Then contracts declared in ``spec/active_recon.md``. Property-based invariants live in ``tests/properties/test_active_recon_properties.py``.*

**Classes:**
- `TestNmapResult` (line 24) `class TestNmapResult`
- `TestNiktoResult` (line 45) `class TestNiktoResult`
- `TestSqlmapResult` (line 58) `class TestSqlmapResult`
- `TestDnsreconResult` (line 71) `class TestDnsreconResult`
- `TestTheHarvesterResult` (line 84) `class TestTheHarvesterResult`
- `TestResultTypes` (line 97) `class TestResultTypes`
- `TestErrorResultsFlowThrough` (line 135) `class TestErrorResultsFlowThrough`

**Methods:**
- `test_run_nmap_returns_result` (line 25) `def test_run_nmap_returns_result(self)`
- `test_nmap_result_has_to_dict` (line 29) `def test_nmap_result_has_to_dict(self)`
- `test_nmap_result_to_entities_is_list` (line 38) `def test_nmap_result_to_entities_is_list(self)`
- `test_run_nikto_returns_result` (line 46) `def test_run_nikto_returns_result(self)`
- `test_nikto_result_has_to_dict` (line 50) `def test_nikto_result_has_to_dict(self)`
- `test_run_sqlmap_returns_result` (line 59) `def test_run_sqlmap_returns_result(self)`
- `test_sqlmap_result_has_to_dict` (line 63) `def test_sqlmap_result_has_to_dict(self)`
- `test_run_dnsrecon_returns_result` (line 72) `def test_run_dnsrecon_returns_result(self)`
- `test_dnsrecon_result_has_to_dict` (line 76) `def test_dnsrecon_result_has_to_dict(self)`
- `test_run_theHarvester_returns_result` (line 85) `def test_run_theHarvester_returns_result(self)`
- `test_harvester_result_has_to_dict` (line 89) `def test_harvester_result_has_to_dict(self)`
- `test_nmap_result_is_dataclass` (line 98) `def test_nmap_result_is_dataclass(self)`
- `test_nikto_result_is_dataclass` (line 106) `def test_nikto_result_is_dataclass(self)`
- `test_sqlmap_result_is_dataclass` (line 113) `def test_sqlmap_result_is_dataclass(self)`
- `test_dnsrecon_result_is_dataclass` (line 120) `def test_dnsrecon_result_is_dataclass(self)`
- `test_harvester_result_is_dataclass` (line 127) `def test_harvester_result_is_dataclass(self)`
- `test_nmap_error_result_has_empty_entities` (line 136) `def test_nmap_error_result_has_empty_entities(self)`

#### `test_async_client.py`
**Path:** `tests/test_async_client.py`
**File Doc:** *BDD tests for egress anonymisation wiring (`estorides_core.async_client`).  Ported from the standalone ``_test_proxy.py`` validator into pytest. Offline: no socket is opened.*

**Classes:**
- `TestSocksDetection` (line 16) `class TestSocksDetection`
- `TestRedaction` (line 23) `class TestRedaction`
- `TestEffectiveProxies` (line 33) `class TestEffectiveProxies`
- `TestRotation` (line 45) `class TestRotation`
- `TestFailClosed` (line 63) `class TestFailClosed`

**Methods:**
- `test_schemes` (line 17) `def test_schemes(self)`
- `test_hides_credentials_keeps_host` (line 24) `def test_hides_credentials_keeps_host(self)`
- `test_passthrough_without_credentials` (line 29) `def test_passthrough_without_credentials(self)`
- `test_explicit_wins` (line 34) `def test_explicit_wins(self, monkeypatch)`
- `test_none_by_default` (line 39) `def test_none_by_default(self, monkeypatch)`
- `test_round_robin` (line 46) `def test_round_robin(self)`
- `test_direct_has_no_proxy` (line 57) `def test_direct_has_no_proxy(self)`
- `test_socks_without_backend_raises` (line 64) `def test_socks_without_backend_raises(self, monkeypatch)`
- `_enter_and_rotate` (line 49) `def _enter_and_rotate()`
- `_enter_socks` (line 68) `def _enter_socks()`

#### `test_audit_log.py`
**Path:** `tests/test_audit_log.py`
**File Doc:** *AuditLog rotation: file size cap, in-place .N rotation (issue #48).*

**Functions:**
- `_ev` (line 12) `def _ev(ts)`
- `test_audit_log_appends` (line 22) `def test_audit_log_appends(tmp_path)`
- `test_audit_log_rotates_when_cap_exceeded` (line 31) `def test_audit_log_rotates_when_cap_exceeded(tmp_path)`
- `test_audit_log_rotation_respects_keep_count` (line 48) `def test_audit_log_rotation_respects_keep_count(tmp_path)`
- `test_audit_log_no_rotation_when_disabled` (line 61) `def test_audit_log_no_rotation_when_disabled(tmp_path)`

#### `test_auth_gate.py`
**Path:** `tests/test_auth_gate.py`
**File Doc:** *Auth gate: bearer-token protection for sensitive /api/* routes.  When `ESTORIDES_AUTH_TOKEN` is set, the gate is on: anonymous callers get 401, callers with the right bearer token get through. When unset, the gate is off and every call passes through (local-trust single-user mode).*

**Functions:**
- `app_with_gate` (line 22) `def app_with_gate(monkeypatch)` - *A Flask app with the auth gate enabled, token 'sek'.

Each test gets a fresh app so the module-level _GATE slot is clean.*
- `test_gate_auto_generates_token_when_unset` (line 41) `def test_gate_auto_generates_token_when_unset(monkeypatch)`
- `test_gate_on_rejects_anonymous` (line 53) `def test_gate_on_rejects_anonymous(app_with_gate)`
- `test_gate_on_accepts_bearer_header` (line 61) `def test_gate_on_accepts_bearer_header(app_with_gate)`
- `test_gate_on_accepts_alt_header` (line 68) `def test_gate_on_accepts_alt_header(app_with_gate)`
- `test_gate_on_accepts_cookie` (line 74) `def test_gate_on_accepts_cookie(app_with_gate)`
- `test_gate_on_rejects_wrong_token` (line 81) `def test_gate_on_rejects_wrong_token(app_with_gate)`
- `test_gate_on_auto_generated_token_in_meta` (line 87) `def test_gate_on_auto_generated_token_in_meta(monkeypatch)`
- `test_gate_on_exposes_token_for_index_meta` (line 95) `def test_gate_on_exposes_token_for_index_meta()`
- `private` (line 34) `def private()`

#### `test_case_crypto.py`
**Path:** `tests/test_case_crypto.py`
**File Doc:** *M4 case_crypto BDD red.*

**Functions:**
- `_fresh_store` (line 5) `def _fresh_store(tmp_path, monkeypatch, key)`
- `test_disabled_stores_plaintext` (line 22) `def test_disabled_stores_plaintext(tmp_path, monkeypatch)`
- `test_enabled_roundtrip` (line 33) `def test_enabled_roundtrip(tmp_path, monkeypatch)`
- `test_bad_key_falls_back` (line 58) `def test_bad_key_falls_back(tmp_path, monkeypatch)`
- `test_mixed_rows_and_tamper` (line 69) `def test_mixed_rows_and_tamper(tmp_path, monkeypatch)`

#### `test_central_config.py`
**Path:** `tests/test_central_config.py`
**File Doc:** *M6 RED tests: central config for orchestrator + tool_install.*

**Functions:**
- `test_config_defaults` (line 7) `def test_config_defaults()`
- `test_run_defaults_track_config` (line 20) `def test_run_defaults_track_config()`
- `test_tool_install_survives_malformed_env` (line 29) `def test_tool_install_survives_malformed_env(monkeypatch)`

#### `test_change_detection.py`
**Path:** `tests/test_change_detection.py`
**File Doc:** *ATDD + BDD tests for estorides_core.change_detection.  Implements the Given-When-Then contracts declared in ``spec/change_detection.md`` (module 2c). Property-based invariants live in ``tests/properties/test_change_detection_properties.py``.  Run from the project root::  .venv/bin/pytest tests/test_change_detection.py -v*

**Classes:**
- `TestNewEntity` (line 56) `class TestNewEntity` - *S1 del spec.*
- `TestPropertyChanged` (line 99) `class TestPropertyChanged` - *S2 del spec.*
- `TestFirstRunBeforeIsNone` (line 125) `class TestFirstRunBeforeIsNone` - *S3 del spec.*
- `TestAfterIsNone` (line 147) `class TestAfterIsNone` - *S4 del spec.*
- `TestDisappearedWithGrace` (line 166) `class TestDisappearedWithGrace` - *S5 del spec.*
- `TestSourceAdded` (line 199) `class TestSourceAdded` - *S6 del spec.*
- `TestMinReliabilityFiltersSources` (line 226) `class TestMinReliabilityFiltersSources` - *S7 del spec.*
- `TestMaxChangesBounds` (line 255) `class TestMaxChangesBounds` - *S8 del spec.*
- `TestProgrammerErrorRaises` (line 281) `class TestProgrammerErrorRaises` - *S9 del spec.*
- `TestHostilePropertyKey` (line 310) `class TestHostilePropertyKey` - *S10 del spec.*
- `TestDeterminism` (line 346) `class TestDeterminism` - *S11 del spec.*
- `TestSourceRemoved` (line 386) `class TestSourceRemoved` - *S13 del spec: una source que antes veía la entity ya no la ve.*
- `TestEdgeChanges` (line 436) `class TestEdgeChanges` - *S14 del spec: edges salientes que aparecen/desaparecen.*
- `TestConfidenceShifted` (line 488) `class TestConfidenceShifted` - *S15 del spec: |confidence_after - confidence_before| > 0.20.*
- `TestBoundedSmoke` (line 528) `class TestBoundedSmoke` - *S12 del spec: smoke test del dataclass.*

**Functions:**
- `_entity` (line 28) `def _entity(eid, etype, value)`

**Methods:**
- `test_one_new_entity_emits_one_new_change` (line 59) `def test_one_new_entity_emits_one_new_change(self)`
- `test_new_change_score_in_high_band` (line 84) `def test_new_change_score_in_high_band(self)`
- `test_property_change_emits_one_change` (line 102) `def test_property_change_emits_one_change(self)`
- `test_before_none_reports_all_as_new` (line 128) `def test_before_none_reports_all_as_new(self)`
- `test_after_none_returns_empty_report` (line 150) `def test_after_none_returns_empty_report(self)`
- `test_disappeared_within_grace_is_ignored` (line 169) `def test_disappeared_within_grace_is_ignored(self)`
- `test_disappeared_outside_grace_emits_change` (line 181) `def test_disappeared_outside_grace_emits_change(self)`
- `test_new_source_on_existing_entity_emits_source_added` (line 202) `def test_new_source_on_existing_entity_emits_source_added(self)`
- `test_min_reliability_e_excludes_f_source` (line 229) `def test_min_reliability_e_excludes_f_source(self)`
- `test_max_changes_caps_output` (line 258) `def test_max_changes_caps_output(self)`
- `test_entity_id_empty_raises` (line 284) `def test_entity_id_empty_raises(self)`
- `test_entity_type_empty_raises` (line 288) `def test_entity_type_empty_raises(self)`
- `test_entity_value_empty_raises` (line 292) `def test_entity_value_empty_raises(self)`
- `test_min_change_score_out_of_range_raises` (line 296) `def test_min_change_score_out_of_range_raises(self)`
- `test_max_changes_too_small_raises` (line 302) `def test_max_changes_too_small_raises(self)`
- `test_hostile_key_does_not_crash` (line 322) `def test_hostile_key_does_not_crash(self, hostile_key)`
- `test_same_input_same_ids_and_scores` (line 349) `def test_same_input_same_ids_and_scores(self)`
- `test_input_order_does_not_affect_output` (line 366) `def test_input_order_does_not_affect_output(self)`
- `test_source_removed_emits_change` (line 389) `def test_source_removed_emits_change(self)`
- `test_source_removed_filtered_by_min_score` (line 410) `def test_source_removed_filtered_by_min_score(self)`
- `test_edge_added_emits_change` (line 439) `def test_edge_added_emits_change(self)`
- `test_edge_removed_emits_change` (line 462) `def test_edge_removed_emits_change(self)`
- `test_large_confidence_shift_emits_change` (line 491) `def test_large_confidence_shift_emits_change(self)`
- `test_small_confidence_shift_ignored` (line 509) `def test_small_confidence_shift_ignored(self)`
- `test_change_is_frozen` (line 531) `def test_change_is_frozen(self)`
- `test_diff_is_frozen` (line 541) `def test_diff_is_frozen(self)`
- `test_change_report_is_frozen` (line 546) `def test_change_report_is_frozen(self)`

#### `test_cli_watch.py`
**Path:** `tests/test_cli_watch.py`
**File Doc:** *BDD / regression tests for the `estorides watch add` CLI path.  Context (boy-scout fix): `cmd_watch_add` used to crash with `AttributeError` (the `watch add` subparser never declared `--proxy` / `--passive-only`) and then `NameError` (`SCHEDULER_ENABLED` undefined). These tests pin the contract so the path can never silently rot again.  - CW1: parser accepts the OPSEC flags on `watch add` - CW2: `cmd_watch_add` persists the watch and starts the scheduler - CW3: a malformed interval < 15 is rejected before any side effect - CW4: the scheduler runner is wired exactly once (idempotent)*

**Classes:**
- `_FakeScheduler` (line 24) `class _FakeScheduler` - *Minimal stand-in for WatchScheduler: records calls, no threads.*
- `TestCW1Parser` (line 59) `class TestCW1Parser`
- `TestCW2Command` (line 69) `class TestCW2Command`
- `TestCW3Validation` (line 92) `class TestCW3Validation`
- `TestCW4RunnerFactory` (line 102) `class TestCW4RunnerFactory`

**Methods:**
- `cli_env` (line 47) `def cli_env(monkeypatch, tmp_path)` - *Isolate the CLI from the real singletons.*
- `__init__` (line 27) `def __init__(self)`
- `has_runner` (line 34) `def has_runner(self)`
- `set_runner` (line 37) `def set_runner(self, runner)`
- `start` (line 41) `def start(self)`
- `test_watch_add_accepts_opsec_flags` (line 60) `def test_watch_add_accepts_opsec_flags(self)`
- `test_watch_add_persists_and_starts` (line 70) `def test_watch_add_persists_and_starts(self, cli_env)`
- `test_watch_add_wires_runner_once` (line 82) `def test_watch_add_wires_runner_once(self, cli_env)`
- `test_interval_below_minimum_rejected` (line 93) `def test_interval_below_minimum_rejected(self, cli_env)`
- `test_runner_factory_returns_awaitable_callable` (line 103) `def test_runner_factory_returns_awaitable_callable(self, cli_env)`
- `test_scheduler_has_runner_property` (line 109) `def test_scheduler_has_runner_property(self, tmp_path)`

#### `test_cloud_asset_discovery.py`
**Path:** `tests/test_cloud_asset_discovery.py`
**File Doc:** *ATDD + BDD tests for estorides_core.cloud_asset_discovery.  Implements the Given-When-Then contracts declared in ``spec/cloud_asset_discovery.md``.*

**Classes:**
- `TestPublicS3Bucket` (line 27) `class TestPublicS3Bucket`
- `TestBucketWithListing` (line 40) `class TestBucketWithListing`
- `TestNoAssets` (line 58) `class TestNoAssets`
- `TestReadOnlyMethods` (line 68) `class TestReadOnlyMethods`
- `TestCloudFrontDetection` (line 77) `class TestCloudFrontDetection`
- `TestFirebaseDiscovery` (line 90) `class TestFirebaseDiscovery`
- `TestBucketNamePermutations` (line 103) `class TestBucketNamePermutations`
- `TestRateLimiting` (line 127) `class TestRateLimiting`

**Functions:**
- `_make_asset` (line 16) `def _make_asset(provider, url, accessible, listing)`

**Methods:**
- `test_accessible_bucket_returned` (line 28) `def test_accessible_bucket_returned(self)`
- `test_listing_bucket_has_files` (line 41) `def test_listing_bucket_has_files(self)`
- `test_empty_when_no_cloud_assets` (line 59) `def test_empty_when_no_cloud_assets(self)`
- `test_assess_bucket_uses_get_only` (line 69) `def test_assess_bucket_uses_get_only(self)`
- `test_cloudfront_cname_detected` (line 78) `def test_cloudfront_cname_detected(self)`
- `test_firebase_accessible` (line 91) `def test_firebase_accessible(self)`
- `test_generates_common_permutations` (line 104) `def test_generates_common_permutations(self)`
- `test_strips_tld_for_permutations` (line 114) `def test_strips_tld_for_permutations(self)`
- `test_rate_limit_constant_defined` (line 128) `def test_rate_limit_constant_defined(self)`

#### `test_code_exposure.py`
**Path:** `tests/test_code_exposure.py`
**File Doc:** *ATDD + BDD tests for estorides_core.code_exposure.  Implements the Given-When-Then contracts declared in ``spec/code_exposure.md``.*

**Classes:**
- `TestAwsKeyDetection` (line 17) `class TestAwsKeyDetection`
- `TestNoFindings` (line 38) `class TestNoFindings`
- `TestRateLimited` (line 47) `class TestRateLimited`
- `TestInternalUrlDetection` (line 54) `class TestInternalUrlDetection`
- `TestEnvFileDetection` (line 62) `class TestEnvFileDetection`
- `TestPlaceholderCredentials` (line 70) `class TestPlaceholderCredentials`
- `TestMultiPlatform` (line 81) `class TestMultiPlatform`
- `TestSnippetBounded` (line 95) `class TestSnippetBounded`
- `TestClassifyFindingTypes` (line 103) `class TestClassifyFindingTypes`

**Methods:**
- `test_detects_aws_key_as_critical` (line 18) `def test_detects_aws_key_as_critical(self)`
- `test_validates_aws_key_format` (line 32) `def test_validates_aws_key_format(self)`
- `test_empty_when_no_repos` (line 39) `def test_empty_when_no_repos(self)`
- `test_returns_partial_on_rate_limit` (line 48) `def test_returns_partial_on_rate_limit(self)`
- `test_internal_url_is_high_severity` (line 55) `def test_internal_url_is_high_severity(self)`
- `test_env_file_is_critical_config` (line 63) `def test_env_file_is_critical_config(self)`
- `test_placeholder_marked_info` (line 71) `def test_placeholder_marked_info(self)`
- `test_example_key_marked_info` (line 75) `def test_example_key_marked_info(self)`
- `test_aggregates_multi_source` (line 82) `def test_aggregates_multi_source(self)`
- `test_snippet_under_200_chars` (line 96) `def test_snippet_under_200_chars(self)`
- `test_ssh_key_detection` (line 104) `def test_ssh_key_detection(self)`
- `test_api_key_detection` (line 109) `def test_api_key_detection(self)`

#### `test_config_env.py`
**Path:** `tests/test_config_env.py`
**File Doc:** *BDD / regression tests for fault-tolerant configuration parsing.  Doctrine (`config.py` docstring): a malformed env var must fall back to the default and log, never crash the process at import time. These tests pin that contract for every typed reader and for the import-time constant surface.  - CE1: typed readers parse valid values - CE2: a malformed value falls back to the default - CE3: a malformed env var cannot crash `import estorides_core.config` - CE4: CSV lists are whitespace-stripped*

**Classes:**
- `TestCE1TypedReaders` (line 33) `class TestCE1TypedReaders`
- `TestCE2MalformedFallsBack` (line 47) `class TestCE2MalformedFallsBack`
- `TestCE3ImportNeverCrashes` (line 76) `class TestCE3ImportNeverCrashes`
- `TestCE4CsvStripped` (line 92) `class TestCE4CsvStripped`

**Functions:**
- `_run_with_env` (line 23) `def _run_with_env(env_overrides, code)`

**Methods:**
- `test_env_int_parses` (line 34) `def test_env_int_parses(self, monkeypatch)`
- `test_env_float_parses` (line 40) `def test_env_float_parses(self, monkeypatch)`
- `test_env_int_garbage` (line 48) `def test_env_int_garbage(self, monkeypatch)`
- `test_env_float_garbage` (line 54) `def test_env_float_garbage(self, monkeypatch)`
- `test_env_bool_unknown_uses_default` (line 60) `def test_env_bool_unknown_uses_default(self, monkeypatch)`
- `test_env_bool_tokens` (line 67) `def test_env_bool_tokens(self, monkeypatch)`
- `test_malformed_numeric_env_does_not_break_import` (line 77) `def test_malformed_numeric_env_does_not_break_import(self)`
- `test_exact_dedup_keys_are_stripped` (line 93) `def test_exact_dedup_keys_are_stripped(self)`

#### `test_csp_safe_styles.py`
**Path:** `tests/test_csp_safe_styles.py`
**File Doc:** *CSP-safe styles: regression tests for the inline-style bug introduced by the CSP tightening in commit 45c3af5.  The contract:  * The HTML template must not contain any `style="..."` attributes. Browsers block those under the locked-down `style-src` policy. * The JS bundle must not emit `style="..."` strings in any template literal — those would be re-injected into the DOM as inline styles and blocked the same way. * Dynamic per-cluster / per-kind colouring must go through the CSSOM (`el.style.background = cs`), which CSP does *not* restrict. * The CSP itself must not have been relaxed back to `'unsafe-inline'`. * The eight offscreen elements must use the HTML5 `hidden` attribute (which the browser's user-agent stylesheet turns into `display:none`), not a literal `style="display:none"`.  These tests parse the files on disk; they do not require a running app. A complementary Flask-level test renders `index.html` through a test client to confirm the same holds in the served output.*

**Functions:**
- `_strip_template_jinja` (line 41) `def _strip_template_jinja(template_text)` - *Replace `{{ ... }}` and `{% ... %}` with empty so the file is grep-able.

We only care whether the *static markup* contains `style="..."`. A
`{{ estorides_auth_token or '' }}` cannot produce a `style="` because
the auth token is a short opaque string and Jinja escapes it
(autoescape=on by default for .html). Even if the token were
malicious, a separate test (S7) covers the injection vector.*
- `_strip_js_comments_and_strings_outside_templates` (line 55) `def _strip_js_comments_and_strings_outside_templates(js_text)` - *Return the *template-literal contents* of the JS file as a single string.

We can't simply grep `style="` because of false positives in comments
and regular strings (e.g. error messages). The CSP issue is
specifically about values written into the DOM via `innerHTML`
containing `style="`. The only way to do that in this codebase is
through a template literal that gets assigned to `innerHTML`,
`outerHTML`, or inserted via `insertAdjacentHTML`.

Strategy: strip line (`// …`) and block (`/* … */`) comments
first so backticks inside comments don't confuse the template
literal scanner. Then extract every backtick-delimited template
literal and return them joined.*
- `test_index_html_has_no_style_attribute` (line 90) `def test_index_html_has_no_style_attribute()` - *S1 — `style="..."` must not appear anywhere in the rendered template.

Browsers reject inline style attributes under the locked-down
`style-src` policy. The fix is to use CSS classes (or `hidden`) —
not to relax the policy.*
- `test_estorides_js_has_no_style_in_template_literals` (line 111) `def test_estorides_js_has_no_style_in_template_literals()` - *S2 — `style="..."` must not appear in any template literal in the JS.

JS uses `innerHTML = `…`` to inject HTML. The literal text must
not contain `style="…"` because the browser will then try to
apply that style attribute and CSP will block it.*
- `test_offscreen_element_uses_hidden_attribute` (line 145) `def test_offscreen_element_uses_hidden_attribute(element_id)` - *S3 — Each offscreen element must have the HTML5 `hidden` attribute.

The browser's user-agent stylesheet already turns `[hidden]` into
`display: none`, so we don't need a literal `style="display:none"`.*
- `test_css_has_required_class` (line 189) `def test_css_has_required_class(selector)` - *The CSS file must define the new classes the refactor relies on.*
- `test_csp_policy_does_not_relax_for_unsafe_inline` (line 201) `def test_csp_policy_does_not_relax_for_unsafe_inline()` - *S5 — The locked-down CSP must stay locked down.

The whole point of the refactor is that we don't have to relax
`style-src` to make the UI work. If this test ever fails, the
previous fix was reverted to `'unsafe-inline'` — a regression.*
- `test_csp_policy_is_unchanged_after_refactor` (line 225) `def test_csp_policy_is_unchanged_after_refactor()` - *S6 — The default CSP string is byte-identical to the pre-refactor value.

The fix is in the frontend, not in the policy. If this test fails,
the policy was changed instead of refactored.*
- `test_dynamic_cluster_color_uses_cssom_assignment` (line 252) `def test_dynamic_cluster_color_uses_cssom_assignment()` - *S4 — The bridge-tooltip chip must set background via CSSOM.

We assert that the JS code *does* set `chip.style.background = cs`
(or `span.style.backgroundColor = cs`) and that the HTML string
for the chip is built without a `style="background:…"` attribute.*
- `test_dynamic_kind_color_uses_cssom_assignment` (line 272) `def test_dynamic_kind_color_uses_cssom_assignment()` - *S4 (kind) — `colorForKind(e.kind)` must reach CSSOM, not innerHTML.*
- `test_rendered_template_has_no_style_attribute_and_uses_hidden` (line 289) `def test_rendered_template_has_no_style_attribute_and_uses_hidden()` - *End-to-end: render `index.html` and assert no inline styles leak.*

#### `test_encrypted_export.py`
**Path:** `tests/test_encrypted_export.py`
**File Doc:** *Encrypted export: plaintext must be removed after encryption.  Issue #8 reported that `export_stix_encrypted` and `export_misp_encrypted` left a plaintext STIX/MISP bundle on disk in `reports/`. The fix is to delete the plaintext in a `finally` block so it always goes away, even when encryption fails. The `age` binary is mocked in these tests so the suite is hermetic.*

**Classes:**
- `_FakeCompleted` (line 21) `class _FakeCompleted`

**Methods:**
- `_kg_with_one_node` (line 27) `def _kg_with_one_node()`
- `_patch_age_ok` (line 33) `def _patch_age_ok()` - *Pretend `age` is on PATH and that `age -e -r ...` produced ciphertext.*
- `test_stix_encrypted_removes_plaintext` (line 43) `def test_stix_encrypted_removes_plaintext(tmp_path)`
- `test_misp_encrypted_removes_plaintext` (line 52) `def test_misp_encrypted_removes_plaintext(tmp_path)`
- `test_stix_encrypted_removes_plaintext_on_failure` (line 61) `def test_stix_encrypted_removes_plaintext_on_failure(tmp_path)` - *Even when age fails, the plaintext must be removed.*
- `__init__` (line 22) `def __init__(self, rc, stderr)`
- `_run` (line 35) `def _run(cmd, stdin, stdout, stderr, check)`
- `_run_fail` (line 66) `def _run_fail(cmd, stdin, stdout, stderr, check)`

#### `test_entity_extraction.py`
**Path:** `tests/test_entity_extraction.py`
**File Doc:** *BDD / regression tests for entity extraction and fuzzy merge.  - EE1: `types=[]` extracts nothing (and `types=None` extracts everything) - EE2: fuzzy merge picks the most-observed spelling as canonical*

**Classes:**
- `TestEE1TypesFilter` (line 12) `class TestEE1TypesFilter`
- `TestEE2FuzzyCanonical` (line 28) `class TestEE2FuzzyCanonical`

**Methods:**
- `test_none_means_all` (line 15) `def test_none_means_all(self)`
- `test_empty_list_means_nothing` (line 20) `def test_empty_list_means_nothing(self)`
- `test_subset_restricts` (line 23) `def test_subset_restricts(self)`
- `test_most_observed_spelling_wins` (line 29) `def test_most_observed_spelling_wins(self)` - *Given two spellings of the same domain, the widely-seen one is
canonical even when it is not the shortest.*
- `test_merge_unions_sources` (line 41) `def test_merge_unions_sources(self)`

#### `test_entity_resolution.py`
**Path:** `tests/test_entity_resolution.py`
**File Doc:** *BDD tests for estorides_core.entity_resolution.  These tests implement the Given-When-Then contracts declared in spec/entity_resolution.md. They must fail against the unwritten implementation (red step) and pass after the green step.*

**Classes:**
- `TestTransliteration` (line 42) `class TestTransliteration` - *Cyrillic, Greek, Arabic, diacritic folding.*
- `TestJaroWinkler` (line 68) `class TestJaroWinkler` - *Jaro-Winkler similarity invariants.*
- `TestNormalization` (line 89) `class TestNormalization` - *Type-aware normalisation.*
- `TestCanonicalId` (line 133) `class TestCanonicalId` - *Deterministic content-addressed ids.*
- `TestCrossScriptPersonFusion` (line 155) `class TestCrossScriptPersonFusion` - *ER1: latin + cyrillic + comma-variant → one identity.*
- `TestDomainCaseVariantMerge` (line 194) `class TestDomainCaseVariantMerge` - *ER2: EvilCorp.com and evilcorp.com → exact merge.*
- `TestLookAlikeDomainsSurfaceAsLink` (line 221) `class TestLookAlikeDomainsSurfaceAsLink` - *ER3: evilcorp.com vs evil-corp.com → SAME_AS link, not merged.*
- `TestDeterministicTypeNoFuzzyMatch` (line 246) `class TestDeterministicTypeNoFuzzyMatch` - *ER4: md5 differing by one char → separate entities.*
- `TestIdenticalIpsMerge` (line 272) `class TestIdenticalIpsMerge` - *ER5: same IP from two sources → merged.*
- `TestNearIpsNeverFuse` (line 289) `class TestNearIpsNeverFuse` - *ER6: 8.8.8.8 and 8.8.4.4 → separate.*
- `TestOrgSuffixFolding` (line 309) `class TestOrgSuffixFolding` - *ER7: Evil Corp LLC + Evil Corp → merged.*
- `TestDistinctPersonsStaySeparate` (line 326) `class TestDistinctPersonsStaySeparate` - *ER8: Putin and Medvedev → separate identities.*
- `TestCanonicalEntityRoundtrip` (line 347) `class TestCanonicalEntityRoundtrip` - *ER9: to_dict and to_entity preserve data.*
- `TestEmptyInput` (line 382) `class TestEmptyInput` - *ER10: empty list → empty result.*
- `TestCanonicalIdDeterministic` (line 394) `class TestCanonicalIdDeterministic` - *ER11: same input → same id.*
- `TestDifferentInputDifferentId` (line 406) `class TestDifferentInputDifferentId` - *ER12: different input → different id.*
- `TestEdgeCases` (line 418) `class TestEdgeCases` - *Additional edge cases beyond the spec scenarios.*
- `TestCrossRunStability` (line 448) `class TestCrossRunStability` - *Canonical id stays stable across runs via persistent store.*

**Functions:**
- `_ent` (line 28) `def _ent(etype, value, source, confidence)`
- `_by_value` (line 32) `def _by_value(result, value)`

**Methods:**
- `test_cyrillic_to_latin` (line 45) `def test_cyrillic_to_latin(self)`
- `test_greek_accented_to_latin` (line 48) `def test_greek_accented_to_latin(self)`
- `test_diacritic_fold` (line 51) `def test_diacritic_fold(self)`
- `test_consonant_skeleton_arabic_matches_latin` (line 54) `def test_consonant_skeleton_arabic_matches_latin(self)`
- `test_consonant_skeleton_gemination` (line 57) `def test_consonant_skeleton_gemination(self)`
- `test_distinct_names_have_distinct_skeletons` (line 60) `def test_distinct_names_have_distinct_skeletons(self)`
- `test_non_latin_detector` (line 63) `def test_non_latin_detector(self)`
- `test_identical_strings_score_one` (line 71) `def test_identical_strings_score_one(self)`
- `test_empty_pair_scores_zero` (line 74) `def test_empty_pair_scores_zero(self)`
- `test_classic_jaro_winkler_bound` (line 77) `def test_classic_jaro_winkler_bound(self)`
- `test_dissimilar_strings_score_low` (line 81) `def test_dissimilar_strings_score_low(self)`
- `test_scores_stay_in_unit_interval` (line 84) `def test_scores_stay_in_unit_interval(self)`
- `test_ipv4_normalised` (line 92) `def test_ipv4_normalised(self)`
- `test_ipv6_compressed` (line 95) `def test_ipv6_compressed(self)`
- `test_hash_lowered` (line 101) `def test_hash_lowered(self)`
- `test_cve_uppered` (line 107) `def test_cve_uppered(self)`
- `test_domain_strips_scheme_www_path` (line 110) `def test_domain_strips_scheme_www_path(self)`
- `test_person_order_independent` (line 116) `def test_person_order_independent(self)`
- `test_org_suffix_stripped` (line 121) `def test_org_suffix_stripped(self)`
- `test_asn_normalised` (line 126) `def test_asn_normalised(self)`
- `test_email_lowered` (line 129) `def test_email_lowered(self)`
- `test_deterministic` (line 136) `def test_deterministic(self)`
- `test_different_values_different_ids` (line 141) `def test_different_values_different_ids(self)`
- `test_id_format` (line 146) `def test_id_format(self)`
- `test_three_spellings_fuse` (line 158) `def test_three_spellings_fuse(self)`
- `test_fused_identity_carries_all_sources` (line 169) `def test_fused_identity_carries_all_sources(self)`
- `test_cross_script_flagged_in_attributes` (line 180) `def test_cross_script_flagged_in_attributes(self)`
- `test_domain_case_variants_merge` (line 197) `def test_domain_case_variants_merge(self)`
- `test_domain_merge_is_exact` (line 207) `def test_domain_merge_is_exact(self)`
- `test_look_alike_domains_stay_separate` (line 224) `def test_look_alike_domains_stay_separate(self)`
- `test_look_alike_domains_produce_same_as_link` (line 233) `def test_look_alike_domains_produce_same_as_link(self)`
- `test_deterministic_near_miss_never_matches` (line 249) `def test_deterministic_near_miss_never_matches(self)`
- `test_score_pair_deterministic_mismatch` (line 259) `def test_score_pair_deterministic_mismatch(self)`
- `test_identical_ips_merge` (line 275) `def test_identical_ips_merge(self)`
- `test_near_ips_stay_separate` (line 292) `def test_near_ips_stay_separate(self)`
- `test_org_suffix_variants_merge` (line 312) `def test_org_suffix_variants_merge(self)`
- `test_distinct_persons_not_absorbed` (line 329) `def test_distinct_persons_not_absorbed(self)`
- `test_to_dict_serialises` (line 350) `def test_to_dict_serialises(self)`
- `test_to_entity_projects_legacy` (line 361) `def test_to_entity_projects_legacy(self)`
- `test_resolution_result_has_one_entity` (line 373) `def test_resolution_result_has_one_entity(self)`
- `test_empty_input_returns_empty` (line 385) `def test_empty_input_returns_empty(self)`
- `test_same_normalised_same_id` (line 397) `def test_same_normalised_same_id(self)`
- `test_different_normalised_different_id` (line 409) `def test_different_normalised_different_id(self)`
- `test_blank_value_does_not_crash` (line 421) `def test_blank_value_does_not_crash(self)`
- `test_whitespace_only_handled` (line 425) `def test_whitespace_only_handled(self)`
- `test_single_entity_produces_one_canonical` (line 429) `def test_single_entity_produces_one_canonical(self)`
- `test_confidence_boosted_by_multiple_sources` (line 434) `def test_confidence_boosted_by_multiple_sources(self)`
- `test_cross_run_id_stability` (line 451) `def test_cross_run_id_stability(self)`

#### `test_envutil.py`
**Path:** `tests/test_envutil.py`
**File Doc:** *BDD tests for the shared env readers (spec/envutil.md).*

**Classes:**
- `TestEnvInt` (line 7) `class TestEnvInt`
- `TestEnvFloat` (line 20) `class TestEnvFloat`
- `TestEnvBool` (line 28) `class TestEnvBool`
- `TestEnvCsv` (line 40) `class TestEnvCsv`

**Methods:**
- `test_parse` (line 8) `def test_parse(self, monkeypatch)`
- `test_malformed` (line 12) `def test_malformed(self, monkeypatch)`
- `test_absent` (line 16) `def test_absent(self)`
- `test_parse_and_malformed` (line 21) `def test_parse_and_malformed(self, monkeypatch)`
- `test_tokens` (line 29) `def test_tokens(self, monkeypatch)`
- `test_unknown_uses_default` (line 35) `def test_unknown_uses_default(self, monkeypatch)`
- `test_strip_and_filter` (line 41) `def test_strip_and_filter(self, monkeypatch)`
- `test_default` (line 45) `def test_default(self)`

#### `test_event_bus.py`
**Path:** `tests/test_event_bus.py`
**File Doc:** *M2a event_bus BDD red.*

**Functions:**
- `test_subscribe_publish_order_and_count` (line 7) `def test_subscribe_publish_order_and_count()`
- `test_raising_handler_does_not_block_others` (line 19) `def test_raising_handler_does_not_block_others()`
- `test_payload_isolation_between_handlers` (line 34) `def test_payload_isolation_between_handlers()`
- `test_invalid_inputs_raise` (line 45) `def test_invalid_inputs_raise()`
- `test_singleton_and_unsubscribe` (line 57) `def test_singleton_and_unsubscribe()`
- `bad` (line 22) `def bad(p)`

#### `test_fusion_analytics.py`
**Path:** `tests/test_fusion_analytics.py`
**File Doc:** *ATDD + BDD tests for estorides_core.fusion_analytics.  Implements the Given-When-Then contracts from spec/fusion_analytics.md (module 2e). Must FAIL at the red step (implementation doesn't exist yet) and PASS after the green step.  Run from project root: .venv/bin/pytest tests/test_fusion_analytics.py -v*

**Classes:**
- `TestEntityTimeline` (line 64) `class TestEntityTimeline`
- `TestEntitySummary` (line 87) `class TestEntitySummary`
- `TestEntityTimelineNonexistent` (line 109) `class TestEntityTimelineNonexistent`
- `TestSourceStats` (line 118) `class TestSourceStats`
- `TestMultiSourceConsensus` (line 155) `class TestMultiSourceConsensus`
- `TestCorroboratedProperties` (line 176) `class TestCorroboratedProperties`
- `TestEntitySearch` (line 195) `class TestEntitySearch`
- `TestTopChanged` (line 230) `class TestTopChanged`
- `TestSourceCorroborationMatrix` (line 250) `class TestSourceCorroborationMatrix`
- `TestWithNoneStore` (line 268) `class TestWithNoneStore`
- `TestBoundaryConditions` (line 281) `class TestBoundaryConditions`

**Functions:**
- `store_and_analytics` (line 27) `def store_and_analytics(tmp_path)`
- `_populate_evilcorp` (line 42) `def _populate_evilcorp(store)`
- `_register_source` (line 57) `def _register_source(store, name)`

**Methods:**
- `test_returns_full_timeline` (line 65) `def test_returns_full_timeline(self, store_and_analytics)`
- `test_nonexistent_eid_returns_none` (line 79) `def test_nonexistent_eid_returns_none(self, store_and_analytics)`
- `test_returns_summary_stats` (line 88) `def test_returns_summary_stats(self, store_and_analytics)`
- `test_nonexistent_eid_returns_none` (line 101) `def test_nonexistent_eid_returns_none(self, store_and_analytics)`
- `test_returns_none` (line 110) `def test_returns_none(self, store_and_analytics)`
- `test_returns_source_metrics` (line 119) `def test_returns_source_metrics(self, store_and_analytics)`
- `test_nonexistent_source_returns_none` (line 135) `def test_nonexistent_source_returns_none(self, store_and_analytics)`
- `test_success_rate_correct` (line 139) `def test_success_rate_correct(self, store_and_analytics)`
- `test_consensus_picks_majority_value` (line 156) `def test_consensus_picks_majority_value(self, store_and_analytics)`
- `test_nonexistent_key_returns_empty` (line 165) `def test_nonexistent_key_returns_empty(self, store_and_analytics)`
- `test_filters_by_min_sources` (line 177) `def test_filters_by_min_sources(self, store_and_analytics)`
- `test_min_sources_one_returns_all` (line 185) `def test_min_sources_one_returns_all(self, store_and_analytics)`
- `test_search_by_term` (line 196) `def test_search_by_term(self, store_and_analytics)`
- `test_search_no_results` (line 208) `def test_search_no_results(self, store_and_analytics)`
- `test_search_filter_by_type` (line 213) `def test_search_filter_by_type(self, store_and_analytics)`
- `test_search_with_confidence_and_source_filters` (line 219) `def test_search_with_confidence_and_source_filters(self, store_and_analytics)`
- `test_returns_recently_active_entities` (line 231) `def test_returns_recently_active_entities(self, store_and_analytics)`
- `test_empty_window_returns_empty` (line 241) `def test_empty_window_returns_empty(self, store_and_analytics)`
- `test_returns_pairs_with_shared_counts` (line 251) `def test_returns_pairs_with_shared_counts(self, store_and_analytics)`
- `test_all_methods_return_empty` (line 269) `def test_all_methods_return_empty(self)`
- `test_min_sources_zero_treated_as_one` (line 282) `def test_min_sources_zero_treated_as_one(self, store_and_analytics)`
- `test_negative_days_treated_as_one` (line 288) `def test_negative_days_treated_as_one(self, store_and_analytics)`

#### `test_graph_bundle.py`
**Path:** `tests/test_graph_bundle.py`
**File Doc:** *graph_bundle: pestana Bundles circular 2D + esferica 3D estilo ReadMenator.  Escenarios del contrato `spec/graph_bundle.md`: S1 happy path (dos comunidades), S2 edge (vacio + unassigned), S3 error (scaffolding/rotas fuera, no-dict falla cerrado), S4 seguridad (truncado + JSON-safe), S5 determinismo + settings.*

**Functions:**
- `_force` (line 26) `def _force()`
- `test_s1_two_communities_circle_and_sphere` (line 62) `def test_s1_two_communities_circle_and_sphere()` - *S1 — 3 entidades en 2 grupos, 2 aristas, angulos y esferas validos.*
- `test_s2_empty_and_unassigned` (line 83) `def test_s2_empty_and_unassigned()` - *S2 — vacio sin raise; sin comunidad forma grupo trailing.*
- `test_s3_scaffolding_dropped_and_bad_node_raises` (line 104) `def test_s3_scaffolding_dropped_and_bad_node_raises()` - *S3 — member_of/layered_as/huerfanas/self-loop fuera; no-dict TypeError.*
- `test_s4_hostile_labels_truncated_and_bytes_raise` (line 124) `def test_s4_hostile_labels_truncated_and_bytes_raise()` - *S4 — label 5KB truncado a <=512; bytes hace TypeError; dumps OK.*
- `test_s5_deterministic_and_settings_match_readmenator` (line 149) `def test_s5_deterministic_and_settings_match_readmenator()` - *S5 — dos pasadas identicas; p/u unitarios; settings con defaults.*
- `test_s7_circle_curves_endpoints_and_beta` (line 169) `def test_s7_circle_curves_endpoints_and_beta()` - *S7 — curvas con extremos cerca de las hojas; beta=0 pega al acorde.*
- `test_s7_sphere_caps_hubs_and_fibonacci` (line 205) `def test_s7_sphere_caps_hubs_and_fibonacci()` - *S7 — caps exactos, hubs dentro, hojas unitarias, fibonacci borde.*
- `test_s7_helpers_unit` (line 228) `def test_s7_helpers_unit()` - *S7 — _unit(0)=polo, _round digitos, _opt/_req ramas.*
- `test_s8_settings_full_defaults_and_env` (line 253) `def test_s8_settings_full_defaults_and_env(monkeypatch)` - *S8 — dict exacto con env limpio; override y malformado no tumban.*
- `test_s8_exact_payload_shape` (line 288) `def test_s8_exact_payload_shape()` - *S8 — grupos/nodos/edges byte-exactos sobre el fixture S1.*
- `test_s8_lenient_fields_and_ignored_nodes` (line 308) `def test_s8_lenient_fields_and_ignored_nodes()` - *S8 — None/no-str van a defaults; tiers y communities raras fuera.*
- `test_s9_typeerror_messages_asserted` (line 351) `def test_s9_typeerror_messages_asserted()` - *S9 — los TypeError llevan el mensaje exacto (mata mutantes de texto).*
- `test_s9_bare_entity_defaults_and_idless` (line 374) `def test_s9_bare_entity_defaults_and_idless()` - *S9 — entidad pelada usa todos los defaults; sin id el id es ''.*
- `test_s9_family_fallback_color_and_order` (line 397) `def test_s9_family_fallback_color_and_order()` - *S9 — sin color manda family; comunidad alta antes que unassigned.*
- `test_s9_unit_edge_norms` (line 418) `def test_s9_unit_edge_norms()` - *S9 — _unit en normas limite (mata <= frente a <).*
- `test_s9_fibonacci_exact` (line 427) `def test_s9_fibonacci_exact()` - *S9 — lattice exacto (angulo dorado y reparto vertical pineados).*
- `test_s9_sphere_caps_exact_and_defaults` (line 443) `def test_s9_sphere_caps_exact_and_defaults()` - *S9 — caps exactos; llamada sin radius usa 1.0.*
- `test_s9_default_layout_checksum_and_length` (line 457) `def test_s9_default_layout_checksum_and_length()` - *S9 — checksum de curva con defaults (beta/samples/inner_ratio pineados).*
- `test_s9_all_env_vars_propagate` (line 474) `def test_s9_all_env_vars_propagate(monkeypatch)` - *S9 — cada ESTORIDES_BUNDLE_* llega a su clave (nombres pineados).*
- `test_s10_full_literal_output` (line 507) `def test_s10_full_literal_output()` - *S10 — payload S1 byte-exacto (cada clave/valor pineado).*
- `test_s10_empty_label_falls_back_to_id_and_findings` (line 539) `def test_s10_empty_label_falls_back_to_id_and_findings()` - *S10 — label '' usa el id; findings no-cero viaja.*
- `test_s10_odd_community_ids` (line 553) `def test_s10_odd_community_ids()` - *S10 — 'community:9:9' se descarta; sin label el grupo es 'cluster N'.*
- `test_s10_scaffold_between_entities_dropped` (line 580) `def test_s10_scaffold_between_entities_dropped()` - *S10 — member_of entre entidades se filtra por tipo, no por extremos.*
- `test_s10_community_colors_survive_bad_id` (line 599) `def test_s10_community_colors_survive_bad_id()` - *S10 — un id malo no aborta el resto (continue, no break).*
- `test_s10_env_divergence_changes_layout` (line 623) `def test_s10_env_divergence_changes_layout(monkeypatch)` - *S10 — GROUP_GAP/INNER_RATIO del env mandan (kwargs no opcionales).*
- `test_s10_bspline_short_branch_exact` (line 636) `def test_s10_bspline_short_branch_exact()` - *S10 — rama <3 puntos: interpolacion lineal exacta + longitudes.*
- `test_s10_beta_clamp_and_skip_order` (line 651) `def test_s10_beta_clamp_and_skip_order()` - *S10 — beta>1 se clamp a 1; arista mala primera no aborta.*
- `test_s10_empty_layouts_full_and_absolute_leaves` (line 668) `def test_s10_empty_layouts_full_and_absolute_leaves()` - *S10 — layouts vacios exactos; hojas absolutas (centro/radio pineados).*
- `test_s10_three_group_caps_and_tie_and_axis` (line 692) `def test_s10_three_group_caps_and_tie_and_axis()` - *S10 — 3 grupos: reparto exacto; empate [1,4,1] corta en 1; eje con lattice sintetico.*

#### `test_graph_bundle_assets.py`
**Path:** `tests/test_graph_bundle_assets.py`
**File Doc:** *graph_bundle S6: assets frontend (fuera del sandbox mutmut).  Este archivo NO entra en la seleccion de mutmut (solo mira tests/test_graph_bundle.py): el sandbox `mutants/` no copia templates/static y estos asserts fallarian alli por FileNotFound.*

**Functions:**
- `test_s6_frontend_assets_exist_and_are_csp_clean` (line 19) `def test_s6_frontend_assets_exist_and_are_csp_clean()` - *S6 — tab Bundles + JS sin innerHTML/eval + CSS con tokens.*

#### `test_graph_force3d.py`
**Path:** `tests/test_graph_force3d.py`
**File Doc:** *graph_force3d: port del sistema de grafos ReadMenator + contexto IA.  Escenarios del contrato `spec/graph_force3d.md`: S1 happy path (payload RAW), S2 edge (vacio), S3 error/truncacion determinista con puentes primero, S4 seguridad (no JSON-safe -> TypeError), S5 seguridad (contexto IA acotado y sin inyeccion markdown), S6 settings y colores deterministas.*

**Functions:**
- `_nodes` (line 22) `def _nodes()`
- `_edges` (line 36) `def _edges()`
- `_clusters` (line 45) `def _clusters()`
- `test_s1_build_force_payload_shape` (line 53) `def test_s1_build_force_payload_shape()` - *S1 — 3 nodos + 2 edges producen entidades, comunidades y tiers.*
- `test_s2_empty_graph_no_raise` (line 79) `def test_s2_empty_graph_no_raise()` - *S2 — payload vacio y contexto 'empty graph' sin excepciones.*
- `test_s3_truncation_deterministic_bridge_first` (line 90) `def test_s3_truncation_deterministic_bridge_first()` - *S3 — dos pasadas identicas; el puente sobrevive al corte.*
- `test_s4_non_json_safe_raises_typeerror` (line 112) `def test_s4_non_json_safe_raises_typeerror()` - *S4 — set/bytes en labels -> TypeError, nunca payload parcial.*
- `test_s5_ai_context_budget_and_no_markdown_injection` (line 130) `def test_s5_ai_context_budget_and_no_markdown_injection()` - *S5 — budget duro, fences pares, marcador truncated.*
- `test_s6_family_color_deterministic_and_settings_match_readmenator` (line 149) `def test_s6_family_color_deterministic_and_settings_match_readmenator()` - *S6 — mismo HSL siempre; settings con los 19 valores ReadMenator.*

#### `test_graph_rag_search.py`
**Path:** `tests/test_graph_rag_search.py`
**File Doc:** *graph_rag_search: BM25 + PageRank/PPR + map-reduce global (contrato S1-S6).*

**Functions:**
- `_nodes` (line 18) `def _nodes()`
- `_edges` (line 29) `def _edges()`
- `_clusters` (line 36) `def _clusters()`
- `_searcher` (line 43) `def _searcher()`
- `test_s1_local_match_leads` (line 48) `def test_s1_local_match_leads()` - *S1 — query con match lexico va en local y la entidad lidera.*
- `test_s2_global_hints_map_reduce` (line 61) `def test_s2_global_hints_map_reduce()` - *S2 — hints de overview eligen map-reduce con reportes.*
- `test_s3_empty_graph_no_raise` (line 71) `def test_s3_empty_graph_no_raise()` - *S3 — indice vacio responde sin romper; el mensaje local es explicito.*
- `test_s4_hostile_fails_closed_and_bounded` (line 83) `def test_s4_hostile_fails_closed_and_bounded()` - *S4 — set/bytes -> TypeError; fuzzinlinea acotado con fences pares.*
- `test_s5_pagerank_sums_to_one_and_deterministic` (line 108) `def test_s5_pagerank_sums_to_one_and_deterministic()` - *S5 — PR suma 1, seed lidera PPR, todo byte-identico entre pasadas.*
- `test_s6_context_block_from_digraph` (line 124) `def test_s6_context_block_from_digraph()` - *S6 — bloque de prompt desde nx.DiGraph, acotado; vacio -> ''.*
- `test_s7_web_helper_uses_graph_path_fail_soft` (line 142) `def test_s7_web_helper_uses_graph_path_fail_soft(tmp_path, monkeypatch)` - *S7 — `_graph_rag_block` lee el grafo y falla a '' sin grafo.

El ignore de ResourceWarning es deliberado: este test no abre DBs;
con `filterwarnings=error` el GC puede atribuirle conexiones sqlite
sin cerrar dejadas por otros tests de la suite (víctima aleatoria).*

#### `test_hardening.py`
**Path:** `tests/test_hardening.py`
**File Doc:** *BDD tests for the v1.3 hardening surface, case diff and report.  Ported from the standalone ``_test_hardening.py`` validator into pytest so CI runs it. Offline.*

**Classes:**
- `TestSecurityHeaders` (line 35) `class TestSecurityHeaders`
- `TestCors` (line 48) `class TestCors`
- `TestDebugKillswitch` (line 64) `class TestDebugKillswitch`
- `TestCaseDiff` (line 72) `class TestCaseDiff`
- `TestReport` (line 107) `class TestReport`
- `TestConsoleScript` (line 137) `class TestConsoleScript`

**Functions:**
- `_secured_app` (line 23) `def _secured_app(cfg)`

**Methods:**
- `status` (line 29) `def status()`
- `test_headers_and_body_cap` (line 36) `def test_headers_and_body_cap(self)`
- `test_default_off` (line 49) `def test_default_off(self)`
- `test_allowlist` (line 54) `def test_allowlist(self)`
- `test_debug_raises` (line 65) `def test_debug_raises(self)`
- `test_diff_counts` (line 73) `def test_diff_counts(self, tmp_path)`
- `test_set_notes` (line 97) `def test_set_notes(self, tmp_path)`
- `test_renders_sections` (line 108) `def test_renders_sections(self)`
- `test_with_diff` (line 122) `def test_with_diff(self)`
- `test_help` (line 138) `def test_help(self)`

#### `test_hypothesis_engine.py`
**Path:** `tests/test_hypothesis_engine.py`
**File Doc:** *ATDD + BDD tests for estorides_core.hypothesis_engine.  Implements the Given-When-Then contracts declared in ``spec/hypothesis_engine.md`` (module 2b). Property-based invariants live in ``tests/properties/test_hypothesis_engine_properties.py``.  Run from the project root::  .venv/bin/pytest tests/test_hypothesis_engine.py -v*

**Classes:**
- `TestHappyPathDomainBelongsToActor` (line 39) `class TestHappyPathDomainBelongsToActor` - *S1 del spec.*
- `TestEmptyInputProducesEmptyOutput` (line 113) `class TestEmptyInputProducesEmptyOutput` - *S2 del spec.*
- `TestMalformedObservationsAreSkipped` (line 138) `class TestMalformedObservationsAreSkipped` - *S3 del spec.*
- `TestUnknownSourceFallsBackToReliabilityC` (line 176) `class TestUnknownSourceFallsBackToReliabilityC` - *S4 del spec.*
- `TestMinScoreFiltersHypotheses` (line 199) `class TestMinScoreFiltersHypotheses` - *S5 del spec.*
- `TestMaxHypothesesBounds` (line 230) `class TestMaxHypothesesBounds` - *S6 del spec.*
- `TestProgrammerErrorRaisesValueOrTypeError` (line 253) `class TestProgrammerErrorRaisesValueOrTypeError` - *S7 del spec.*
- `TestHostileObservationPayloadIsHandled` (line 280) `class TestHostileObservationPayloadIsHandled` - *S8 del spec.*
- `TestDeterminism` (line 309) `class TestDeterminism` - *S9 del spec.*
- `TestBoundedSmoke` (line 351) `class TestBoundedSmoke` - *S10 del spec: smoke test del dataclass.*

**Functions:**
- `_obs` (line 23) `def _obs(source, parsed, raw)` - *Build a minimal observation dict that matches the orchestrator shape.*

**Methods:**
- `generate_hypothences_safe` (line 127) `def generate_hypothences_safe(observations, entities)`
- `test_emits_domain_belongsto_actor_hypothesis` (line 42) `def test_emits_domain_belongsto_actor_hypothesis(self)`
- `test_score_in_high_band` (line 61) `def test_score_in_high_band(self)`
- `test_supporting_has_three_items` (line 76) `def test_supporting_has_three_items(self)`
- `test_sources_sorted_and_unique` (line 95) `def test_sources_sorted_and_unique(self)`
- `test_empty_observations_empty_entities` (line 116) `def test_empty_observations_empty_entities(self)`
- `test_empty_observations_only` (line 119) `def test_empty_observations_only(self)`
- `test_empty_entities_only` (line 122) `def test_empty_entities_only(self)`
- `test_observation_with_none_parsed_is_ignored` (line 141) `def test_observation_with_none_parsed_is_ignored(self)`
- `test_observation_without_source_is_ignored` (line 155) `def test_observation_without_source_is_ignored(self)`
- `test_unknown_source_uses_reliability_c` (line 179) `def test_unknown_source_uses_reliability_c(self)`
- `test_min_score_zero_returns_all` (line 202) `def test_min_score_zero_returns_all(self)`
- `test_min_score_one_filters_everything` (line 214) `def test_min_score_one_filters_everything(self)`
- `test_max_hypotheses_caps_output` (line 233) `def test_max_hypotheses_caps_output(self)`
- `test_observations_must_be_sequence` (line 256) `def test_observations_must_be_sequence(self)`
- `test_entities_must_be_sequence` (line 260) `def test_entities_must_be_sequence(self)`
- `test_min_score_out_of_range_raises` (line 264) `def test_min_score_out_of_range_raises(self)`
- `test_max_hypotheses_too_small_raises` (line 270) `def test_max_hypotheses_too_small_raises(self)`
- `test_hostile_value_is_truncated_or_skipped` (line 292) `def test_hostile_value_is_truncated_or_skipped(self, hostile)`
- `test_same_input_same_ids_and_scores` (line 312) `def test_same_input_same_ids_and_scores(self)`
- `test_input_order_does_not_affect_output` (line 333) `def test_input_order_does_not_affect_output(self)`
- `test_hypothesis_dataclass_is_frozen` (line 354) `def test_hypothesis_dataclass_is_frozen(self)`
- `test_evidence_dataclass_is_frozen` (line 370) `def test_evidence_dataclass_is_frozen(self)`
- `test_entity_ref_is_frozen` (line 382) `def test_entity_ref_is_frozen(self)`

#### `test_ids.py`
**Path:** `tests/test_ids.py`
**File Doc:** *BDD tests for the shared deterministic id helper (spec/ids.md).  - ID1: deterministic and content-addressed - ID2: length is honoured - ID3: different payloads do not collide in the 16-char space (smoke)*

**Classes:**
- `TestID1Determinism` (line 15) `class TestID1Determinism`
- `TestID2Length` (line 27) `class TestID2Length`
- `TestID3Distinct` (line 33) `class TestID3Distinct`

**Methods:**
- `test_same_payload_same_id` (line 16) `def test_same_payload_same_id(self)`
- `test_matches_legacy_formula` (line 19) `def test_matches_legacy_formula(self)`
- `test_length_honoured` (line 28) `def test_length_honoured(self)`
- `test_distinct_payloads` (line 34) `def test_distinct_payloads(self)`

#### `test_job_registry.py`
**Path:** `tests/test_job_registry.py`
**File Doc:** *BoundedJobRegistry: size cap + TTL eviction (issues #14, #20, #50).*

**Functions:**
- `test_register_returns_value` (line 11) `def test_register_returns_value()`
- `test_size_cap_evicts_oldest` (line 17) `def test_size_cap_evicts_oldest()`
- `test_get_refreshes_lru_order` (line 28) `def test_get_refreshes_lru_order()`
- `test_ttl_eviction` (line 39) `def test_ttl_eviction()`
- `test_pop_removes_entry` (line 53) `def test_pop_removes_entry()`
- `test_keys_values_consistent` (line 61) `def test_keys_values_consistent()`
- `test_invalid_construction` (line 69) `def test_invalid_construction()`
- `test_replacement_does_not_evict` (line 76) `def test_replacement_does_not_evict()` - *Re-registering the same key keeps the size stable and LRU order intact.*

#### `test_keyless_sources.py`
**Path:** `tests/test_keyless_sources.py`
**File Doc:** *M3 RED tests: keyless BGPView + CISA KEV sources and total parsers.*

**Functions:**
- `_registry` (line 9) `def _registry()`
- `test_bgpview_ip_source` (line 17) `def test_bgpview_ip_source()`
- `test_bgpview_asn_source` (line 25) `def test_bgpview_asn_source()`
- `test_cisa_kev_source` (line 32) `def test_cisa_kev_source()`
- `test_parse_bgpview_extracts` (line 39) `def test_parse_bgpview_extracts()`
- `test_parsers_total_on_hostile_input` (line 53) `def test_parsers_total_on_hostile_input()`
- `test_parse_cisa_kev_extracts` (line 62) `def test_parse_cisa_kev_extracts()`

#### `test_lan_bundle.py`
**Path:** `tests/test_lan_bundle.py`
**File Doc:** *BDD tests for lan_bundle (spec/lan_bundle.md). Written before code.*

**Functions:**
- `_devs` (line 9) `def _devs()`
- `test_s1_bundle_graph_and_lazyown` (line 28) `def test_s1_bundle_graph_and_lazyown()`
- `test_s2_empty` (line 37) `def test_s2_empty()`
- `test_s3_stem_traversal_sanitized` (line 43) `def test_s3_stem_traversal_sanitized()`
- `test_s4_stix_shapes` (line 47) `def test_s4_stix_shapes()`
- `test_s5_write_atomic` (line 53) `def test_s5_write_atomic(tmp_path)`
- `test_s6_error_dir` (line 68) `def test_s6_error_dir(tmp_path)`
- `test_s7_scope_tagged` (line 75) `def test_s7_scope_tagged()`
- `test_s8_deterministic` (line 81) `def test_s8_deterministic()`

#### `test_lan_inventory.py`
**Path:** `tests/test_lan_inventory.py`
**File Doc:** *BDD tests for lan_inventory (spec/lan_inventory.md). Written before code.*

**Functions:**
- `test_s1_arp_parses` (line 6) `def test_s1_arp_parses()`
- `test_s2_dhcp_parses_hostname` (line 13) `def test_s2_dhcp_parses_hostname()`
- `test_s3_merge_same_mac` (line 19) `def test_s3_merge_same_mac()`
- `test_s4_merge_no_mac_by_ip` (line 33) `def test_s4_merge_no_mac_by_ip()`
- `test_s5_garbage_empty` (line 46) `def test_s5_garbage_empty()`
- `test_s6_public_dropped` (line 51) `def test_s6_public_dropped()`
- `test_s7_mac_formats_equal` (line 58) `def test_s7_mac_formats_equal()`
- `test_s9_dead_entries_dropped` (line 64) `def test_s9_dead_entries_dropped()`
- `test_s10_nmap_xml` (line 99) `def test_s10_nmap_xml()`
- `test_s11_nmap_grepable` (line 107) `def test_s11_nmap_grepable()`
- `test_s12_dispatch_tags_source` (line 114) `def test_s12_dispatch_tags_source()`
- `test_s13_hostile_xml_rejected` (line 125) `def test_s13_hostile_xml_rejected()`
- `test_s10_cross_source_fold` (line 130) `def test_s10_cross_source_fold()`
- `test_s8_contact_map` (line 144) `def test_s8_contact_map()`

#### `test_lan_policy.py`
**Path:** `tests/test_lan_policy.py`
**File Doc:** *BDD tests for lan_policy (spec/lan_policy.md). Written before code.*

**Functions:**
- `test_s1_passive_allowed_with_consent` (line 6) `def test_s1_passive_allowed_with_consent()`
- `test_s2_active_allowed_on_triple_gate` (line 16) `def test_s2_active_allowed_on_triple_gate(monkeypatch)`
- `test_s3_checkbox_without_env_ceiling_denies_active` (line 27) `def test_s3_checkbox_without_env_ceiling_denies_active(monkeypatch)`
- `test_s4_valid_private_scope_echoes` (line 37) `def test_s4_valid_private_scope_echoes()`
- `test_s5_no_consent_blocks_even_passive` (line 49) `def test_s5_no_consent_blocks_even_passive()`
- `test_s6_public_scope_blocked` (line 58) `def test_s6_public_scope_blocked()`
- `test_s7_truthy_string_consent_blocked` (line 70) `def test_s7_truthy_string_consent_blocked()`
- `test_s8_unknown_contact_denied_and_capped` (line 79) `def test_s8_unknown_contact_denied_and_capped()`

#### `test_lan_runner.py`
**Path:** `tests/test_lan_runner.py`
**File Doc:** *BDD tests for lan_runner (spec/lan_runner.md). Written before code.*

**Classes:**
- `_FakeToolResult` (line 63) `class _FakeToolResult`

**Functions:**
- `test_s1_passive_injected` (line 10) `def test_s1_passive_injected(tmp_path)`
- `test_s2_no_consent_refused` (line 22) `def test_s2_no_consent_refused(tmp_path)`
- `test_s3_bad_scope_refused` (line 32) `def test_s3_bad_scope_refused(tmp_path)`
- `test_s4_degrade_active` (line 41) `def test_s4_degrade_active(tmp_path, monkeypatch)`
- `test_s5_active_injected` (line 53) `def test_s5_active_injected(tmp_path, monkeypatch)`

**Methods:**
- `test_s9_tool_order` (line 69) `def test_s9_tool_order()`
- `test_s6_empty_readers` (line 102) `def test_s6_empty_readers(tmp_path)`
- `test_s7_scope_auto` (line 112) `def test_s7_scope_auto(tmp_path)`
- `test_s8_no_scope_found` (line 121) `def test_s8_no_scope_found(tmp_path)`
- `__init__` (line 64) `def __init__(self, exit_code, stdout)`
- `ok_nmap` (line 74) `def ok_nmap(tool, args)`
- `fail_then_arp` (line 81) `def fail_then_arp(tool, args)`
- `both_fail` (line 93) `def both_fail(tool, args)`

#### `test_lan_scope.py`
**Path:** `tests/test_lan_scope.py`
**File Doc:** *BDD tests for lan_scope (spec/lan_scope.md). Written before code.*

**Functions:**
- `test_s1_v4_parses_to_network` (line 6) `def test_s1_v4_parses_to_network()`
- `test_s2_v6_ula_accepted` (line 13) `def test_s2_v6_ula_accepted()`
- `test_s3_mixed_keeps_private_only` (line 18) `def test_s3_mixed_keeps_private_only()`
- `test_s4_garbage_returns_none` (line 29) `def test_s4_garbage_returns_none()`
- `test_s5_public_returns_none` (line 33) `def test_s5_public_returns_none()`
- `test_s6_wide_scope_rejected` (line 37) `def test_s6_wide_scope_rejected()`
- `test_s7_empty_default_none` (line 41) `def test_s7_empty_default_none()`
- `test_s8_reader_fail_soft` (line 45) `def test_s8_reader_fail_soft(monkeypatch)`

#### `test_lan_web.py`
**Path:** `tests/test_lan_web.py`
**File Doc:** *BDD tests for lan_web (spec/lan_web.md). Written before code.*

**Functions:**
- `test_s1_passive_permitted` (line 6) `def test_s1_passive_permitted()`
- `test_s2_active_permitted` (line 12) `def test_s2_active_permitted(monkeypatch)`
- `test_s3_checkbox_without_ceiling_degrades` (line 21) `def test_s3_checkbox_without_ceiling_degrades(monkeypatch)`
- `test_s3b_pure_passive_has_no_notice` (line 32) `def test_s3b_pure_passive_has_no_notice()`
- `test_s4_no_consent_refused` (line 37) `def test_s4_no_consent_refused()`
- `test_s5_bad_scope_400` (line 43) `def test_s5_bad_scope_400()`
- `test_s6_string_consent_refused` (line 49) `def test_s6_string_consent_refused()`
- `test_s7_body_not_dict` (line 54) `def test_s7_body_not_dict()`
- `test_s9_lan_request_detection` (line 59) `def test_s9_lan_request_detection()`
- `test_s10_scope_payload` (line 66) `def test_s10_scope_payload()`
- `test_s11_active_intent_permitted` (line 74) `def test_s11_active_intent_permitted(monkeypatch)`
- `test_s8_scope_echo_normalized` (line 84) `def test_s8_scope_echo_normalized()`

#### `test_map_basemap.py`
**Path:** `tests/test_map_basemap.py`
**File Doc:** *BDD tests for spec/map_basemap.md (CARTO basemap, no direct OSM tiles).*

**Functions:**
- `_js` (line 10) `def _js()`
- `test_no_direct_osm_tiles_S1` (line 14) `def test_no_direct_osm_tiles_S1()`
- `test_esri_dark_provider_S2` (line 22) `def test_esri_dark_provider_S2()`
- `test_attribution_S3` (line 35) `def test_attribution_S3()`
- `test_csp_allows_tile_host_S5` (line 41) `def test_csp_allows_tile_host_S5()`

#### `test_monitoring.py`
**Path:** `tests/test_monitoring.py`
**File Doc:** *BDD tests for Monitoring & Advanced Recon modules.  Covers: - M1: Create a watch target - M2: Watch runs and completes - M3: Alert sent on completion - M4: Disable a watch - M5: Delete a watch - M9: Webhook alert channel - Advanced recon source YAML validation*

**Classes:**
- `TestM1CreateWatch` (line 58) `class TestM1CreateWatch` - *M1: Watch target creation.*
- `TestM2WatchRun` (line 86) `class TestM2WatchRun` - *M2: Watch execution and completion.*
- `TestM4DisableWatch` (line 115) `class TestM4DisableWatch` - *M4: Disabling a watch prevents it from running.*
- `TestM5DeleteWatch` (line 142) `class TestM5DeleteWatch` - *M5: Deleting a watch removes it from the store.*
- `TestWatchHistory` (line 164) `class TestWatchHistory` - *Watch run history recording.*
- `TestM9WebhookAlert` (line 194) `class TestM9WebhookAlert` - *M9: Webhook alert channel sends correctly structured payload.*
- `TestAdvancedReconSources` (line 211) `class TestAdvancedReconSources` - *Verify that the new recon source YAMLs are valid and loadable.*
- `TestAlerterChannels` (line 260) `class TestAlerterChannels` - *Alert channel configuration checks.*
- `TestWatchTargetDataclass` (line 292) `class TestWatchTargetDataclass` - *WatchTarget creation and serialisation.*
- `TestSourceCount` (line 324) `class TestSourceCount` - *Verify that new sources are properly counted.*

**Functions:**
- `tmp_store` (line 32) `def tmp_store()` - *Create a temporary WatchStore for testing, auto-closes on teardown.*
- `sample_watch` (line 42) `def sample_watch()`

**Methods:**
- `test_create_watch` (line 61) `def test_create_watch(self, tmp_store)` - *Given a valid query, a watch is created with status pending.*
- `test_watch_next_run_in_future` (line 69) `def test_watch_next_run_in_future(self, sample_watch)` - *Given a watch, next_run_at is in the future.*
- `test_watch_appears_in_list` (line 73) `def test_watch_appears_in_list(self, tmp_store, sample_watch)` - *Given a persisted watch, it appears in the watch list.*
- `test_due_watches_returns_enabled` (line 89) `def test_due_watches_returns_enabled(self, tmp_store)` - *Given enabled watches past due, only those are returned.*
- `test_interval_honored` (line 101) `def test_interval_honored(self, tmp_store)` - *Given a watch with interval 15, due returns it at the right time.*
- `test_disable_does_not_delete` (line 118) `def test_disable_does_not_delete(self, tmp_store, sample_watch)` - *Given a disabled watch, it still exists in the store.*
- `test_disabled_not_due` (line 127) `def test_disabled_not_due(self, tmp_store)` - *Given a disabled watch past due, it is not returned as due.*
- `test_delete_removes_watch` (line 145) `def test_delete_removes_watch(self, tmp_store, sample_watch)` - *Given a deleted watch, it no longer appears.*
- `test_delete_removes_from_list` (line 151) `def test_delete_removes_from_list(self, tmp_store, sample_watch)` - *Given a deleted watch, the list no longer contains it.*
- `test_record_run_start` (line 167) `def test_record_run_start(self, tmp_store, sample_watch)` - *Given a watch, recording a run returns a history id.*
- `test_history_appears` (line 173) `def test_history_appears(self, tmp_store, sample_watch)` - *Given a completed run, history shows it.*
- `test_history_empty_for_new_watch` (line 183) `def test_history_empty_for_new_watch(self, tmp_store, sample_watch)` - *Given a watch with no runs, history is empty.*
- `test_webhook_builds_correct_payload` (line 197) `def test_webhook_builds_correct_payload(self)` - *Given a webhook alert, the payload has the right structure.*
- `test_source_yaml_loads` (line 220) `def test_source_yaml_loads(self, source_name)` - *Given a source YAML file, it loads without errors.*
- `test_source_requires_key` (line 235) `def test_source_requires_key(self, source_name, key_env)` - *Given a source YAML, it declares the correct key_env.*
- `test_all_new_sources_passive` (line 244) `def test_all_new_sources_passive(self)` - *Given the new sources, all are contact: none (passive).*
- `test_available_channels_returns_all` (line 263) `def test_available_channels_returns_all(self)` - *Given the dispatcher, available_channels returns all channel types.*
- `test_channel_env_vars_listed` (line 274) `def test_channel_env_vars_listed(self)` - *Given available channels, each has its env_var listed.*
- `test_unknown_channel_returns_false` (line 281) `def test_unknown_channel_returns_false(self)` - *Given an unknown channel name, send returns False.*
- `test_to_dict_roundtrip` (line 295) `def test_to_dict_roundtrip(self)` - *Given a WatchTarget, to_dict and from_dict roundtrip.*
- `test_default_next_run_set` (line 308) `def test_default_next_run_set(self)` - *Given a WatchTarget with no next_run_at, it defaults to future.*
- `test_default_channels_empty` (line 313) `def test_default_channels_empty(self)` - *Given a WatchTarget with no channels, channels is empty.*
- `test_new_sources_loaded` (line 327) `def test_new_sources_loaded(self)` - *Given the source directory, the new sources are loaded and counted.*

#### `test_obs_fts.py`
**Path:** `tests/test_obs_fts.py`
**File Doc:** *Obs FTS BDD.*

**Functions:**
- `_store` (line 5) `def _store(tmp_path, monkeypatch)`
- `test_fts_finds_rare_word` (line 16) `def test_fts_finds_rare_word(tmp_path, monkeypatch)`
- `test_fts_empty_and_injection_safe` (line 33) `def test_fts_empty_and_injection_safe(tmp_path, monkeypatch)`

#### `test_observation_models.py`
**Path:** `tests/test_observation_models.py`
**File Doc:** *estorides_core.observation_models — BDD/TDD contract tests.  Each test is the executable translation of a BDD scenario in `spec/observation_models.md` (O1-O8). The module under test does not exist yet; these tests must fail (import error) until the implementation lands.*

**Classes:**
- `_Hostile` (line 227) `class _Hostile`
- `_Hostile` (line 238) `class _Hostile`
- `_WeirdName` (line 249) `class _WeirdName`
- `_WeirdName` (line 272) `class _WeirdName`

**Functions:**
- `_full_meta` (line 25) `def _full_meta()`
- `_full_obs` (line 39) `def _full_obs()`
- `test_o1_full_observation_validates` (line 57) `def test_o1_full_observation_validates()`
- `test_o2_error_observation_validates` (line 72) `def test_o2_error_observation_validates()`
- `test_o3_missing_required_field_fails` (line 85) `def test_o3_missing_required_field_fails()`
- `test_o4_unknown_meta_key_forbidden` (line 96) `def test_o4_unknown_meta_key_forbidden()`
- `test_o5_wrong_typed_field_fails` (line 106) `def test_o5_wrong_typed_field_fails()`
- `test_o6_oversized_url_truncated_not_failed` (line 116) `def test_o6_oversized_url_truncated_not_failed()`
- `test_o6_oversized_value_rejected` (line 124) `def test_o6_oversized_value_rejected()`
- `test_o6_error_message_does_not_embed_hostile_value` (line 134) `def test_o6_error_message_does_not_embed_hostile_value()`
- `test_o7_confidence_out_of_range_rejected` (line 146) `def test_o7_confidence_out_of_range_rejected(confidence)`
- `test_o7_confidence_at_bounds_accepted` (line 152) `def test_o7_confidence_at_bounds_accepted()`
- `test_o8_run_result_aggregates` (line 163) `def test_o8_run_result_aggregates()`
- `test_o8_run_result_with_error_surfaces_error` (line 182) `def test_o8_run_result_with_error_surfaces_error()`
- `test_security_non_json_safe_value_rejected` (line 198) `def test_security_non_json_safe_value_rejected(field)`
- `test_security_non_json_safe_attributes_rejected` (line 205) `def test_security_non_json_safe_attributes_rejected()`
- `test_security_bytes_top_level_parsed_rejected` (line 212) `def test_security_bytes_top_level_parsed_rejected()`
- `test_security_non_string_dict_key_rejected` (line 219) `def test_security_non_string_dict_key_rejected()`
- `test_security_arbitrary_object_rejected` (line 226) `def test_security_arbitrary_object_rejected()`
- `test_security_nested_object_inside_list_rejected` (line 236) `def test_security_nested_object_inside_list_rejected()` - *A hostile object nested inside a list must also be rejected (recursion).*
- `test_security_arbitrary_object_message_names_the_type` (line 247) `def test_security_arbitrary_object_message_names_the_type()` - *The rejection message must name the offending type, not the input content.*
- `test_security_non_string_key_message_names_the_problem` (line 260) `def test_security_non_string_key_message_names_the_problem()`
- `test_security_arbitrary_object_message_exact` (line 270) `def test_security_arbitrary_object_message_exact()` - *The rejection message for an arbitrary object is the exact contract text.*
- `test_length_caps_are_positive` (line 287) `def test_length_caps_are_positive()`

#### `test_openapi.py`
**Path:** `tests/test_openapi.py`
**File Doc:** *OpenAPI BDD.*

**Functions:**
- `test_openapi_lists_healthz_without_auth_and_run_with_auth` (line 5) `def test_openapi_lists_healthz_without_auth_and_run_with_auth()`

#### `test_ops_observability.py`
**Path:** `tests/test_ops_observability.py`
**File Doc:** *M1 ops_observability — BDD red. Debe fallar hasta implementar módulo.*

**Functions:**
- `test_healthz_no_auth_ok` (line 8) `def test_healthz_no_auth_ok()`
- `test_readyz_ready_and_not_ready` (line 17) `def test_readyz_ready_and_not_ready()`
- `test_metrics_counters_and_render` (line 26) `def test_metrics_counters_and_render()`
- `test_metrics_label_sanitised_no_newline` (line 44) `def test_metrics_label_sanitised_no_newline()`
- `test_json_logs_opt_in_and_legacy` (line 61) `def test_json_logs_opt_in_and_legacy()`
- `test_metrics_thread_safe` (line 74) `def test_metrics_thread_safe()`

#### `test_opsec_contact.py`
**Path:** `tests/test_opsec_contact.py`
**File Doc:** *BDD tests for operator-OPSEC contact classification + passive-only filter.  Ported from the standalone ``_test_passive.py`` validator into pytest. Offline: exercises the registry and the orchestrator selector.*

**Classes:**
- `TestContactLevel` (line 25) `class TestContactLevel`
- `TestRegistryContact` (line 36) `class TestRegistryContact`
- `TestBrokerTagging` (line 53) `class TestBrokerTagging`
- `TestSelectorCeiling` (line 61) `class TestSelectorCeiling`

**Functions:**
- `_registry` (line 19) `def _registry()`

**Methods:**
- `test_ordering` (line 26) `def test_ordering(self)`
- `test_unknown_is_active` (line 29) `def test_unknown_is_active(self)`
- `test_default_is_passive` (line 32) `def test_default_is_passive(self)`
- `test_sources_load` (line 37) `def test_sources_load(self)`
- `test_all_sources_carry_known_class` (line 40) `def test_all_sources_carry_known_class(self)`
- `test_passive_filter_excludes_broker_active` (line 45) `def test_passive_filter_excludes_broker_active(self)`
- `test_hackertarget_probes_are_broker` (line 54) `def test_hackertarget_probes_are_broker(self)`
- `test_selector_drops_broker_even_when_named` (line 62) `def test_selector_drops_broker_even_when_named(self)`
- `test_selector_keeps_broker_without_ceiling` (line 72) `def test_selector_keeps_broker_without_ceiling(self)`

#### `test_orchestrator_fanout.py`
**Path:** `tests/test_orchestrator_fanout.py`
**File Doc:** *M0 RED tests: fanout normalisation + callback parity + no duplicated methods.*

**Functions:**
- `_src` (line 10) `def _src(name, binary)`
- `test_no_duplicated_method_defs` (line 19) `def test_no_duplicated_method_defs()`
- `_norm` (line 28) `def _norm(items, sources)`
- `test_exception_becomes_error_observation` (line 33) `def test_exception_becomes_error_observation()`
- `test_bad_shape_becomes_error_observation` (line 41) `def test_bad_shape_becomes_error_observation()`
- `test_mixed_http_and_system_app_keep_own_source` (line 47) `def test_mixed_http_and_system_app_keep_own_source()`
- `test_execute_source_forwards_on_done_to_system_app` (line 56) `def test_execute_source_forwards_on_done_to_system_app()`
- `test_system_app_failure_fires_on_done` (line 67) `def test_system_app_failure_fires_on_done()`
- `go` (line 68) `def go()`

#### `test_pagination.py`
**Path:** `tests/test_pagination.py`
**File Doc:** *BDD tests for estorides_core.pagination.  These tests verify the pagination strategy config, param building, cursor extraction, and result counting — all pure functions, no I/O.*

**Classes:**
- `TestPageStrategy` (line 21) `class TestPageStrategy` - *PG1: page param increments with each page number.*
- `TestOffsetStrategy` (line 48) `class TestOffsetStrategy` - *PG2: offset advances by page_size each page.*
- `TestCursorStrategy` (line 78) `class TestCursorStrategy` - *PG3: cursor extracted from response body.*
- `TestNoPagination` (line 128) `class TestNoPagination` - *PG4: default config means disabled.*
- `TestPartialPage` (line 151) `class TestPartialPage` - *PG5: fewer results than page_size signals last page.*
- `TestMaxPages` (line 180) `class TestMaxPages` - *PG6: max_pages limits total requests.*
- `TestFromDict` (line 205) `class TestFromDict` - *PaginationConfig construction from raw dict.*

**Methods:**
- `test_first_page_is_one` (line 24) `def test_first_page_is_one(self)`
- `test_second_page_increments` (line 29) `def test_second_page_increments(self)`
- `test_default_param_name` (line 34) `def test_default_param_name(self)`
- `test_no_pagination_returns_empty` (line 39) `def test_no_pagination_returns_empty(self)`
- `test_first_page_offset_zero` (line 51) `def test_first_page_offset_zero(self)`
- `test_second_page_offset_25` (line 56) `def test_second_page_offset_25(self)`
- `test_third_page_offset_50` (line 61) `def test_third_page_offset_50(self)`
- `test_custom_param_names` (line 66) `def test_custom_param_names(self)`
- `test_extracts_cursor_from_simple_path` (line 81) `def test_extracts_cursor_from_simple_path(self)`
- `test_extracts_cursor_from_nested_path` (line 86) `def test_extracts_cursor_from_nested_path(self)`
- `test_missing_path_returns_none` (line 91) `def test_missing_path_returns_none(self)`
- `test_empty_cursor_returns_none` (line 95) `def test_empty_cursor_returns_none(self)`
- `test_null_cursor_returns_none` (line 99) `def test_null_cursor_returns_none(self)`
- `test_non_dict_response_returns_none` (line 103) `def test_non_dict_response_returns_none(self)`
- `test_disabled_strategy_returns_none` (line 107) `def test_disabled_strategy_returns_none(self)`
- `test_build_params_empty_for_cursor` (line 111) `def test_build_params_empty_for_cursor(self)`
- `test_cursor_custom_param` (line 116) `def test_cursor_custom_param(self)`
- `test_default_config_disabled` (line 131) `def test_default_config_disabled(self)`
- `test_empty_dict_disabled` (line 135) `def test_empty_dict_disabled(self)`
- `test_none_disabled` (line 139) `def test_none_disabled(self)`
- `test_enabled_when_strategy_set` (line 143) `def test_enabled_when_strategy_set(self)`
- `test_partial_page_detected` (line 154) `def test_partial_page_detected(self)`
- `test_full_page_not_detected_as_partial` (line 159) `def test_full_page_not_detected_as_partial(self)`
- `test_list_response_counted_directly` (line 164) `def test_list_response_counted_directly(self)`
- `test_using_custom_response_list_path` (line 168) `def test_using_custom_response_list_path(self)`
- `test_default_max_pages` (line 183) `def test_default_max_pages(self)`
- `test_custom_max_pages` (line 187) `def test_custom_max_pages(self)`
- `test_zero_page_size_means_no_check` (line 193) `def test_zero_page_size_means_no_check(self)`
- `test_all_fields_mapped` (line 208) `def test_all_fields_mapped(self)`
- `test_partial_dict_uses_defaults` (line 230) `def test_partial_dict_uses_defaults(self)`
- `test_empty_string_strategy_disabled` (line 236) `def test_empty_string_strategy_disabled(self)`

#### `test_parsers.py`
**Path:** `tests/test_parsers.py`
**File Doc:** *BDD / regression tests for the remote-JSON parser layer.  Doctrine (`parsers.py` header): every parser MUST be total — any unrecognised input returns an empty container, never raises. The orchestrator trusts that contract, so a remote source returning `null` where an object is expected must not abort a run.  - P1: known malformed payloads return empty containers, no raise - P2: valid payloads still parse (no over-eager guard regression)*

**Classes:**
- `TestP1Totality` (line 19) `class TestP1Totality` - *Given a remote payload with nulls/strings where objects belong.*
- `TestP2NoRegression` (line 50) `class TestP2NoRegression`

**Methods:**
- `test_parser_is_total` (line 35) `def test_parser_is_total(self, parser_name, payload)`
- `test_otx_null_entry_skipped` (line 39) `def test_otx_null_entry_skipped(self)`
- `test_keybase_missing_them` (line 43) `def test_keybase_missing_them(self)`
- `test_wayback_non_list_header` (line 46) `def test_wayback_non_list_header(self)`
- `test_otx_valid` (line 51) `def test_otx_valid(self)`
- `test_keybase_valid` (line 58) `def test_keybase_valid(self)`
- `test_ethplorer_valid` (line 72) `def test_ethplorer_valid(self)`

#### `test_pdns_monitor.py`
**Path:** `tests/test_pdns_monitor.py`
**File Doc:** *ATDD + BDD tests for estorides_core.pdns_monitor.  Implements the Given-When-Then contracts declared in ``spec/pdns_monitor.md``.*

**Classes:**
- `TestCtLogSubdomains` (line 18) `class TestCtLogSubdomains`
- `TestIPHistory` (line 32) `class TestIPHistory`
- `TestNoData` (line 47) `class TestNoData`
- `TestNewCertificates` (line 55) `class TestNewCertificates`
- `TestNoAxfr` (line 76) `class TestNoAxfr`
- `TestWildcardCert` (line 85) `class TestWildcardCert`
- `TestSubdomainStatusClassification` (line 102) `class TestSubdomainStatusClassification`
- `TestMonitorPollInterval` (line 111) `class TestMonitorPollInterval`

**Methods:**
- `test_returns_subdomains_from_ct` (line 19) `def test_returns_subdomains_from_ct(self)`
- `test_tracks_ip_changes` (line 33) `def test_tracks_ip_changes(self)`
- `test_empty_when_no_history` (line 48) `def test_empty_when_no_history(self)`
- `test_new_cert_with_san` (line 56) `def test_new_cert_with_san(self)`
- `test_no_zone_transfer_attempted` (line 77) `def test_no_zone_transfer_attempted(self)`
- `test_wildcard_cert_detected` (line 86) `def test_wildcard_cert_detected(self)`
- `test_active_status_when_resolves` (line 103) `def test_active_status_when_resolves(self)`
- `test_inactive_when_no_resolution` (line 106) `def test_inactive_when_no_resolution(self)`
- `test_minimum_poll_interval` (line 112) `def test_minimum_poll_interval(self)`

#### `test_people_intel.py`
**Path:** `tests/test_people_intel.py`
**File Doc:** *ATDD + BDD tests for estorides_core.people_intel.  Implements the Given-When-Then contracts declared in ``spec/people_intel.md``.*

**Classes:**
- `TestHappyPathEmployeeDiscovery` (line 28) `class TestHappyPathEmployeeDiscovery`
- `TestNoEmployees` (line 42) `class TestNoEmployees`
- `TestInvalidDomain` (line 50) `class TestInvalidDomain`
- `TestBreachPasswordContext` (line 57) `class TestBreachPasswordContext`
- `TestEmailPatternInference` (line 81) `class TestEmailPatternInference`
- `TestSingleEmailAmbiguous` (line 101) `class TestSingleEmailAmbiguous`
- `TestCrossBreachCorrelation` (line 108) `class TestCrossBreachCorrelation`
- `TestNoRawPasswords` (line 128) `class TestNoRawPasswords`

**Functions:**
- `_make_emp` (line 18) `def _make_emp(name, role, email)`

**Methods:**
- `test_returns_employees_from_domain` (line 29) `def test_returns_employees_from_domain(self)`
- `test_empty_when_no_employees` (line 43) `def test_empty_when_no_employees(self)`
- `test_rejects_invalid_domain` (line 51) `def test_rejects_invalid_domain(self)`
- `test_breach_with_password_is_critical` (line 58) `def test_breach_with_password_is_critical(self)`
- `test_infers_first_dot_last_pattern` (line 82) `def test_infers_first_dot_last_pattern(self)`
- `test_infers_firstinitial_last_pattern` (line 91) `def test_infers_firstinitial_last_pattern(self)`
- `test_single_email_low_confidence` (line 102) `def test_single_email_low_confidence(self)`
- `test_multiple_breaches_increase_risk` (line 109) `def test_multiple_breaches_increase_risk(self)`
- `test_passwords_not_in_serialised_output` (line 129) `def test_passwords_not_in_serialised_output(self)`

#### `test_probabilistic_fusion.py`
**Path:** `tests/test_probabilistic_fusion.py`
**File Doc:** *BDD + property tests for probabilistic fusion.  These tests verify that :meth:`FusionStore.fuse_entity` and :meth:`FusionStore.fuse_relationship` use the reliability_scoring pipeline (source reliability + source type + corroboration) instead of the simplistic ``MAX()`` merge.*

**Classes:**
- `TestPrimarySourceRaisesLowConf` (line 48) `class TestPrimarySourceRaisesLowConf` - *PF1: tertiary first sighting → primary raises score.*
- `TestTertiaryCannotOverride` (line 77) `class TestTertiaryCannotOverride` - *PF2: untrusted_webscraper (F) cannot beat 0.9 existing.*
- `TestCorroborationLiftsScore` (line 110) `class TestCorroborationLiftsScore` - *PF3: multiple independent sources raise the score.*
- `TestMergeMonotonic` (line 142) `class TestMergeMonotonic` - *PF4: a lower-confidence sighting never decreases the score.*
- `TestFirstSightingWeighted` (line 170) `class TestFirstSightingWeighted` - *PF5: untrusted_webscraper first sighting is heavily discounted.*
- `TestRelationshipBayesian` (line 192) `class TestRelationshipBayesian` - *PF6: relationship from untrusted source cannot override primary.*
- `TestFusionStoreRegression` (line 217) `class TestFusionStoreRegression` - *Existing fusion store invariants must still hold.*

**Functions:**
- `_fs` (line 17) `def _fs()`
- `_teardown` (line 23) `def _teardown(store, tmp)`
- `_entity` (line 30) `def _entity(etype, value, source, confidence)`

**Methods:**
- `test_primary_source_raises_score` (line 51) `def test_primary_source_raises_score(self)`
- `test_untrusted_source_cannot_override` (line 80) `def test_untrusted_source_cannot_override(self)`
- `test_two_sources_are_better_than_one` (line 113) `def test_two_sources_are_better_than_one(self)`
- `test_lower_confidence_never_decreases` (line 145) `def test_lower_confidence_never_decreases(self)`
- `test_untrusted_first_sighting_discounted` (line 173) `def test_untrusted_first_sighting_discounted(self)`
- `test_relationship_untrusted_cannot_override` (line 195) `def test_relationship_untrusted_cannot_override(self)`
- `test_entity_id_deterministic` (line 220) `def test_entity_id_deterministic(self)`
- `test_entity_source_count_tracks` (line 229) `def test_entity_source_count_tracks(self)`
- `test_observation_count_advances` (line 243) `def test_observation_count_advances(self)`
- `test_empty_type_returns_empty` (line 259) `def test_empty_type_returns_empty(self)`
- `test_empty_value_returns_empty` (line 267) `def test_empty_value_returns_empty(self)`
- `test_add_observation_and_stats` (line 275) `def test_add_observation_and_stats(self)`
- `test_register_sources` (line 290) `def test_register_sources(self)`
- `test_search_entities` (line 305) `def test_search_entities(self)`
- `test_corroborated_properties` (line 320) `def test_corroborated_properties(self)`
- `test_get_entity_nonexistent` (line 333) `def test_get_entity_nonexistent(self)`
- `test_open_store_closes_gracefully` (line 340) `def test_open_store_closes_gracefully(self)`

#### `test_query_intent.py`
**Path:** `tests/test_query_intent.py`
**File Doc:** *M1 RED tests: query intent v2.*

**Functions:**
- `test_phone` (line 7) `def test_phone()`
- `test_mac` (line 12) `def test_mac()`
- `test_at_handle` (line 17) `def test_at_handle()`
- `test_url_normalises_to_host` (line 22) `def test_url_normalises_to_host()`
- `test_btc_uppercase_bech32` (line 27) `def test_btc_uppercase_bech32()`
- `test_backward_compat` (line 31) `def test_backward_compat()`

#### `test_recon_fusion.py`
**Path:** `tests/test_recon_fusion.py`
**File Doc:** *BDD tests for the recon_fusion module (Modulo 2g).  Each scenario maps to a Given-When-Then from spec/recon_fusion.md.*

**Classes:**
- `TestS1CriticalCorroborated` (line 46) `class TestS1CriticalCorroborated` - *S1 -- Happy path: corroborated by 5 sources -> CRITICAL.*
- `TestS2TwoReliableSources` (line 65) `class TestS2TwoReliableSources` - *S2 -- 2 sources with A/B reliability -> CRITICAL (via 2+ src + high rel).*
- `TestS3SingleHighReliability` (line 82) `class TestS3SingleHighReliability` - *S3 -- Single high-reliability source -> MEDIUM.*
- `TestS4SingleLowReliability` (line 102) `class TestS4SingleLowReliability` - *S4 -- Single F-reliability source -> NOISE.*
- `TestS5EmptyInput` (line 117) `class TestS5EmptyInput` - *S5 -- No observations or entities -> empty result.*
- `TestS6DirectMatchBoost` (line 130) `class TestS6DirectMatchBoost` - *S6 -- Direct match entity has boosted score.*
- `TestS7EmptyQuery` (line 147) `class TestS7EmptyQuery` - *S7 -- Empty query raises ValueError.*
- `TestS8BadConfig` (line 157) `class TestS8BadConfig` - *S8 -- Inconsistent thresholds raise ValueError.*
- `TestS9NoneObservations` (line 173) `class TestS9NoneObservations` - *S9 -- None observations handled safely.*
- `TestS10EntityWithoutType` (line 183) `class TestS10EntityWithoutType` - *S10 -- Entity without type is ignored.*
- `TestS11Dedup` (line 195) `class TestS11Dedup` - *S11 -- Identical observations deduped to one.*
- `TestS12Ordering` (line 212) `class TestS12Ordering` - *S12 -- Tiers ordered by relevance_score descending.*
- `TestIntegrationMultiEntity` (line 227) `class TestIntegrationMultiEntity` - *Integration: multiple entities classified across tiers.*
- `TestFusionResultDataclass` (line 252) `class TestFusionResultDataclass` - *FusionResult dataclass structural tests.*
- `TestRelevanceTierEnum` (line 267) `class TestRelevanceTierEnum` - *RelevanceTier enum members and ordering.*

**Functions:**
- `_observation` (line 14) `def _observation(source, category, parser, status, parsed)`
- `_entity` (line 31) `def _entity(etype, value, confidence, sources)`

**Methods:**
- `test_critical_with_5_sources` (line 49) `def test_critical_with_5_sources(self)`
- `test_two_reliable_sources_critical` (line 68) `def test_two_reliable_sources_critical(self)`
- `test_single_a_source_is_medium` (line 85) `def test_single_a_source_is_medium(self)`
- `test_single_f_source_is_noise` (line 105) `def test_single_f_source_is_noise(self)`
- `test_empty_observations_and_entities` (line 120) `def test_empty_observations_and_entities(self)`
- `test_direct_match_boosts_score` (line 133) `def test_direct_match_boosts_score(self)`
- `test_empty_query_raises` (line 150) `def test_empty_query_raises(self)`
- `test_bad_thresholds_raise` (line 160) `def test_bad_thresholds_raise(self)`
- `test_none_observations_safe` (line 176) `def test_none_observations_safe(self)`
- `test_entity_without_type_ignored` (line 186) `def test_entity_without_type_ignored(self)`
- `test_identical_observations_deduped` (line 198) `def test_identical_observations_deduped(self)`
- `test_tier_ordered_by_score` (line 215) `def test_tier_ordered_by_score(self)`
- `test_mixed_entities_across_tiers` (line 230) `def test_mixed_entities_across_tiers(self)`
- `test_fusion_result_serialisable` (line 255) `def test_fusion_result_serialisable(self)`
- `test_enum_members` (line 270) `def test_enum_members(self)`
- `test_enum_order_list` (line 277) `def test_enum_order_list(self)`

#### `test_recon_report.py`
**Path:** `tests/test_recon_report.py`
**File Doc:** *ATDD + BDD tests for estorides_export.recon_report.  Implements the Given-When-Then contracts declared in ``spec/recon_report.md``.*

**Classes:**
- `TestFullReport` (line 28) `class TestFullReport`
- `TestExecutiveSummaryFindings` (line 42) `class TestExecutiveSummaryFindings`
- `TestMinimalReport` (line 57) `class TestMinimalReport`
- `TestTlpClassification` (line 72) `class TestTlpClassification`
- `TestRecommendationsOrdered` (line 98) `class TestRecommendationsOrdered`
- `TestCredentialsRedacted` (line 106) `class TestCredentialsRedacted`
- `TestSubdomainTree` (line 119) `class TestSubdomainTree`
- `TestSingleFindingReport` (line 133) `class TestSingleFindingReport`

**Functions:**
- `_make_meta` (line 18) `def _make_meta()`

**Methods:**
- `test_contains_all_sections` (line 29) `def test_contains_all_sections(self)`
- `test_mentions_critical_findings` (line 43) `def test_mentions_critical_findings(self)`
- `test_minimal_report_with_no_findings` (line 58) `def test_minimal_report_with_no_findings(self)`
- `test_tlp_amber_in_header` (line 73) `def test_tlp_amber_in_header(self)`
- `test_classification_propagates_to_exec_summary` (line 81) `def test_classification_propagates_to_exec_summary(self)` - *Given a TLP:RED report, the executive summary must say RED too.

Regression: the executive summary used to hardcode TLP:AMBER while
the header printed the caller's classification, so a RED report
carried two contradicting markings.*
- `test_critical_first` (line 99) `def test_critical_first(self)`
- `test_aws_key_redacted` (line 107) `def test_aws_key_redacted(self)`
- `test_password_redacted` (line 112) `def test_password_redacted(self)`
- `test_ascii_tree_generated` (line 120) `def test_ascii_tree_generated(self)`
- `test_single_finding_prominent` (line 134) `def test_single_finding_prominent(self)`

#### `test_reliability_scoring.py`
**Path:** `tests/test_reliability_scoring.py`
**File Doc:** *ATDD + BDD tests for estorides_core.reliability_scoring.  These tests implement the Given-When-Then contracts declared in ``spec/reliability_scoring.md`` (module 2a). They MUST fail against the unwritten implementation (the red step of the cycle), and they MUST pass after the green step. Property-based invariants live in ``tests/properties/test_reliability_scoring_properties.py``.  Run from the project root::  .venv/bin/pytest tests/test_reliability_scoring.py -v*

**Classes:**
- `TestHappyPathHighReliabilityCorroboratedFresh` (line 47) `class TestHappyPathHighReliabilityCorroboratedFresh` - *S1 del spec.*
- `TestUnknownSourceFallsBackToDefault` (line 111) `class TestUnknownSourceFallsBackToDefault` - *S2 del spec.*
- `TestZeroCorroborationYieldsZeroScore` (line 171) `class TestZeroCorroborationYieldsZeroScore` - *S3 del spec: una sola fuente, no importa lo fiable que sea, score = 0.*
- `TestVeryOldObservationDecaysToZero` (line 190) `class TestVeryOldObservationDecaysToZero` - *S4 del spec.*
- `TestInvalidInputRaisesValueError` (line 227) `class TestInvalidInputRaisesValueError` - *S5 del spec.*
- `TestHostileSourceNameIsHandledSafely` (line 272) `class TestHostileSourceNameIsHandledSafely` - *S6 del spec: input del operador, posiblemente adversario, no rompe.*
- `TestMergeReliableBeatsLessReliable` (line 301) `class TestMergeReliableBeatsLessReliable` - *S7 del spec.*
- `TestMergeUnreliableCannotRaise` (line 368) `class TestMergeUnreliableCannotRaise` - *S8 del spec.*
- `TestDeterminism` (line 414) `class TestDeterminism` - *S9 del spec: misma entrada → misma salida, bit a bit.*
- `TestBoundedSmoke` (line 455) `class TestBoundedSmoke` - *S10 del spec: smoke test del contrato de cota.*
- `TestSourceHierarchyPrimaryBeatsSecondaryBeatsTertiary` (line 485) `class TestSourceHierarchyPrimaryBeatsSecondaryBeatsTertiary` - *S11 del spec: mismo reliability, distinto source_type.*
- `TestSourceHierarchyPrimaryCBeatsTertiaryA` (line 552) `class TestSourceHierarchyPrimaryCBeatsTertiaryA` - *S12 del spec: source type compensates for lower reliability.*
- `TestSourceTypeFromName` (line 596) `class TestSourceTypeFromName` - *S13 del spec: lookup known and unknown source names.*
- `TestSourceHierarchyInMerge` (line 630) `class TestSourceHierarchyInMerge` - *S14 del spec: merge with source type hierarchy.*
- `TestSourceTypeWeightsBounded` (line 664) `class TestSourceTypeWeightsBounded` - *S15 del spec: source_type_weight is always in {1.00, 0.85, 0.60}.*
- `TestReliabilityWeightHelpers` (line 694) `class TestReliabilityWeightHelpers` - *`reliability_weight*` centralise the mapping that used to be copied
into change_detection / recon_fusion / hypothesis_engine.*

**Methods:**
- `test_score_in_high_band` (line 50) `def test_score_in_high_band(self)`
- `test_reliability_weight_is_one` (line 69) `def test_reliability_weight_is_one(self)`
- `test_freshness_weight_is_one_when_age_zero` (line 78) `def test_freshness_weight_is_one_when_age_zero(self)`
- `test_corroboration_weight_matches_log10` (line 88) `def test_corroboration_weight_matches_log10(self)`
- `test_credibility_weight_for_probably_true` (line 97) `def test_credibility_weight_for_probably_true(self)`
- `test_unknown_source_returns_default` (line 122) `def test_unknown_source_returns_default(self, name)`
- `test_unknown_source_produces_weak_score_with_one_corroboration` (line 128) `def test_unknown_source_produces_weak_score_with_one_corroboration(self)`
- `test_unknown_source_with_many_corroborators_reaches_high_band` (line 148) `def test_unknown_source_with_many_corroborators_reaches_high_band(self)`
- `test_zero_corroboration_collapses_score` (line 174) `def test_zero_corroboration_collapses_score(self)`
- `test_one_year_old_with_low_corroboration_decays` (line 193) `def test_one_year_old_with_low_corroboration_decays(self)`
- `test_freshness_is_monotonically_decreasing` (line 206) `def test_freshness_is_monotonically_decreasing(self)` - *Una observación más vieja siempre tiene freshness menor o igual.*
- `test_negative_corroboration_count_raises` (line 230) `def test_negative_corroboration_count_raises(self)`
- `test_negative_observation_age_raises` (line 237) `def test_negative_observation_age_raises(self)`
- `test_base_confidence_out_of_range_raises` (line 245) `def test_base_confidence_out_of_range_raises(self, bad)`
- `test_half_life_zero_raises` (line 252) `def test_half_life_zero_raises(self)`
- `test_half_life_negative_raises` (line 260) `def test_half_life_negative_raises(self)`
- `test_hostile_name_does_not_raise` (line 290) `def test_hostile_name_does_not_raise(self, hostile)`
- `test_new_a_source_raises_score` (line 304) `def test_new_a_source_raises_score(self)`
- `test_merge_takes_max_of_existing_and_new` (line 321) `def test_merge_takes_max_of_existing_and_new(self)`
- `test_merge_keeps_existing_when_new_is_weaker` (line 337) `def test_merge_keeps_existing_when_new_is_weaker(self)`
- `test_merge_result_is_always_bounded` (line 352) `def test_merge_result_is_always_bounded(self)`
- `test_f_source_against_strong_existing_keeps_existing` (line 371) `def test_f_source_against_strong_existing_keeps_existing(self)`
- `test_merge_existing_out_of_range_raises` (line 386) `def test_merge_existing_out_of_range_raises(self)`
- `test_merge_new_observation_out_of_range_raises` (line 398) `def test_merge_new_observation_out_of_range_raises(self)`
- `test_compute_confidence_is_pure` (line 417) `def test_compute_confidence_is_pure(self)`
- `test_merge_confidence_is_pure` (line 430) `def test_merge_confidence_is_pure(self)`
- `test_reliability_weights_match_nato_admiralty` (line 458) `def test_reliability_weights_match_nato_admiralty(self)`
- `test_credibility_weights_match_nato_admiralty` (line 467) `def test_credibility_weights_match_nato_admiralty(self)`
- `test_default_half_life_is_thirty_days` (line 475) `def test_default_half_life_is_thirty_days(self)`
- `test_default_credibility_is_cannot_be_judged` (line 478) `def test_default_credibility_is_cannot_be_judged(self)`
- `test_primary_tertiary_score_order` (line 488) `def test_primary_tertiary_score_order(self)`
- `test_primary_weight_is_one` (line 521) `def test_primary_weight_is_one(self)`
- `test_secondary_weight_is_085` (line 530) `def test_secondary_weight_is_085(self)`
- `test_tertiary_weight_is_060` (line 539) `def test_tertiary_weight_is_060(self)`
- `test_primary_c_beats_tertiary_a` (line 555) `def test_primary_c_beats_tertiary_a(self)`
- `test_primary_c_weight` (line 582) `def test_primary_c_weight(self)`
- `test_rdap_is_primary` (line 599) `def test_rdap_is_primary(self)`
- `test_leakcheck_is_tertiary` (line 602) `def test_leakcheck_is_tertiary(self)`
- `test_wikidata_is_secondary` (line 605) `def test_wikidata_is_secondary(self)`
- `test_shodan_is_secondary` (line 608) `def test_shodan_is_secondary(self)`
- `test_unknown_falls_back_to_default` (line 611) `def test_unknown_falls_back_to_default(self)`
- `test_none_falls_back` (line 614) `def test_none_falls_back(self)`
- `test_empty_falls_back` (line 617) `def test_empty_falls_back(self)`
- `test_hostile_input_does_not_raise` (line 620) `def test_hostile_input_does_not_raise(self)`
- `test_new_primary_raises_score` (line 633) `def test_new_primary_raises_score(self)`
- `test_new_tertiary_does_not_raise_weak_existing` (line 646) `def test_new_tertiary_does_not_raise_weak_existing(self)`
- `test_source_type_weights_are_exact` (line 667) `def test_source_type_weights_are_exact(self)`
- `test_primary_always_gives_one` (line 672) `def test_primary_always_gives_one(self)`
- `test_tertiary_always_gives_060` (line 681) `def test_tertiary_always_gives_060(self)`
- `test_known_source_letter` (line 698) `def test_known_source_letter(self)`
- `test_unknown_source_falls_back_to_default` (line 703) `def test_unknown_source_falls_back_to_default(self)`
- `test_override_wins` (line 708) `def test_override_wins(self)`
- `test_invalid_override_falls_back` (line 711) `def test_invalid_override_falls_back(self)`
- `test_letter_helper` (line 716) `def test_letter_helper(self)`

#### `test_retry_policy.py`
**Path:** `tests/test_retry_policy.py`
**File Doc:** *Retry policy BDD.*

**Functions:**
- `test_delays_geometric_and_capped` (line 5) `def test_delays_geometric_and_capped()`
- `test_client_defaults_to_central_policy` (line 14) `def test_client_defaults_to_central_policy()`

#### `test_scope.py`
**Path:** `tests/test_scope.py`
**File Doc:** *BDD tests for the bug-bounty scope classifier (`estorides_core.scope`).  Ported from the standalone ``_test_scope.py`` validator into the pytest suite so CI actually runs it.*

**Classes:**
- `TestNormaliseAsset` (line 32) `class TestNormaliseAsset`
- `TestParseRules` (line 43) `class TestParseRules`
- `TestClassify` (line 48) `class TestClassify`
- `TestBuildReport` (line 79) `class TestBuildReport`

**Methods:**
- `test_strip_scheme_path_port` (line 33) `def test_strip_scheme_path_port(self)`
- `test_strip_trailing_dot` (line 36) `def test_strip_trailing_dot(self)`
- `test_ipv6_brackets_stripped` (line 39) `def test_ipv6_brackets_stripped(self)`
- `test_skips_blanks_and_comments` (line 44) `def test_skips_blanks_and_comments(self)`
- `test_wildcard_matches_subdomain_and_apex` (line 49) `def test_wildcard_matches_subdomain_and_apex(self)`
- `test_regex_rule` (line 54) `def test_regex_rule(self)`
- `test_cidr_and_single_ip` (line 59) `def test_cidr_and_single_ip(self)`
- `test_out_of_scope_precedence` (line 65) `def test_out_of_scope_precedence(self)`
- `test_foreign_host_unknown` (line 70) `def test_foreign_host_unknown(self)`
- `test_empty_asset_unknown` (line 74) `def test_empty_asset_unknown(self)`
- `test_dedup_hosts_ips_and_buckets` (line 80) `def test_dedup_hosts_ips_and_buckets(self)`

#### `test_search_telemetry.py`
**Path:** `tests/test_search_telemetry.py`
**File Doc:** *BDD/ATDD suite for the `search_telemetry` module (spec/search_telemetry.md).  Each test maps to a Given-When-Then scenario in the spec. The pure-logic scenarios (S1-S6, S9, S10) exercise the module directly; the integration scenarios (S7, S8, S11) render `index.html` through Jinja and assert the served chrome honours the brand/emoji and single-source-of-truth invariants.*

**Functions:**
- `_render_index` (line 36) `def _render_index()` - *Render `index.html` exactly as the web layer does, telemetry included.*
- `test_s1_determinate_progress_midsearch` (line 52) `def test_s1_determinate_progress_midsearch()`
- `test_s2_indeterminate_progress` (line 67) `def test_s2_indeterminate_progress()`
- `test_s3_completion_stops_spinner` (line 79) `def test_s3_completion_stops_spinner()`
- `test_s4_out_of_range_is_clamped` (line 90) `def test_s4_out_of_range_is_clamped()`
- `test_s5_unknown_phase_rejected` (line 103) `def test_s5_unknown_phase_rejected()`
- `test_s6_catalog_is_brand_and_emoji_clean` (line 114) `def test_s6_catalog_is_brand_and_emoji_clean()`
- `test_s7_rendered_template_has_no_third_party_brand` (line 132) `def test_s7_rendered_template_has_no_third_party_brand()`
- `test_s8_rendered_chrome_has_no_emoji` (line 141) `def test_s8_rendered_chrome_has_no_emoji()`
- `test_s9_brand_predicate_boundaries` (line 153) `def test_s9_brand_predicate_boundaries()`
- `_valid_kwargs` (line 163) `def _valid_kwargs()`
- `test_s10_empty_brand_rejected` (line 178) `def test_s10_empty_brand_rejected()`
- `test_s10_no_tips_rejected` (line 185) `def test_s10_no_tips_rejected()`
- `test_s10_duplicate_phase_rejected` (line 192) `def test_s10_duplicate_phase_rejected()`
- `test_s10_emoji_in_catalog_rejected` (line 205) `def test_s10_emoji_in_catalog_rejected()`
- `test_s10_brand_collision_rejected` (line 212) `def test_s10_brand_collision_rejected()`
- `test_s10_missing_sentinel_phase_rejected` (line 219) `def test_s10_missing_sentinel_phase_rejected()`
- `test_s11_template_renders_from_catalog` (line 233) `def test_s11_template_renders_from_catalog()`
- `test_default_telemetry_is_a_shared_instance` (line 247) `def test_default_telemetry_is_a_shared_instance()`

#### `test_security_remediation.py`
**Path:** `tests/test_security_remediation.py`

**Classes:**
- `TestSsrfLogSanitisation` (line 25) `class TestSsrfLogSanitisation` - *BDD S1, S2: ssrf_guard._resolve log messages must not contain the
hostname or exception detail.*
- `TestInfoExposureEncryption` (line 75) `class TestInfoExposureEncryption` - *BDD S3, S4: encryption error endpoints must not leak str(e).*
- `TestInfoExposureSourceOps` (line 176) `class TestInfoExposureSourceOps` - *BDD S5, S6: source file operations must not leak str(e).*
- `TestHttpsRedirectSafety` (line 260) `class TestHttpsRedirectSafety` - *BDD S7: the HTTPS redirect must use request.host, not request.url.*
- `TestCiWorkflowPermissions` (line 306) `class TestCiWorkflowPermissions` - *BDD S10: .github/workflows/ci.yml must have permissions: read-all.*
- `TestOsirisExceptionSafety` (line 336) `class TestOsirisExceptionSafety` - *BDD S11: osiris endpoint exceptions must return generic error.*
- `TestWebSecurityRedirect` (line 399) `class TestWebSecurityRedirect` - *Verify the HTTP-to-HTTPS redirect in web_security.py is safe.*
- `TestJavaScriptDomSafety` (line 431) `class TestJavaScriptDomSafety` - *BDD S8, S9: verify no innerHTML with unsanitized data in JS.*
- `TestAlerterSsrf` (line 498) `class TestAlerterSsrf` - *Alert webhook URLs (incl. user-supplied `channel` URLs) are validated
by the central SSRF guard before any socket opens. Treats all webhook
destinations as hostile.*
- `TestAlerterNoRedirect` (line 538) `class TestAlerterNoRedirect` - *A webhook that 302-redirects to link-local/cloud-metadata must NOT be
followed: the SSRF guard only vetted the first hop.*
- `TestTooltipSinkHardening` (line 592) `class TestTooltipSinkHardening` - *DOMPurify pipeline: remote text reaches the DOM only through
DOMPurify.sanitize (the sanitizer CodeQL models), with a fail-closed
plain-text fallback when the vendored library is missing.*
- `_Fake302` (line 555) `class _Fake302`
- `_FakeOpener` (line 564) `class _FakeOpener`

**Methods:**
- `test_dns_failure_log_omits_hostname` (line 29) `def test_dns_failure_log_omits_hostname(self, caplog)`
- `test_dns_failure_log_omits_ip_in_hostname` (line 43) `def test_dns_failure_log_omits_ip_in_hostname(self, caplog)`
- `test_dns_failure_log_contains_host_length_not_host` (line 55) `def test_dns_failure_log_contains_host_length_not_host(self, caplog)`
- `app` (line 79) `def app(self)`
- `_make_export_route` (line 86) `def _make_export_route(self, app, raise_val, error_msg, status)`
- `_make_export_route_fixed` (line 118) `def _make_export_route_fixed(self, app, raise_val, error_msg, status)`
- `test_encryption_valueerror_leaks_detail` (line 151) `def test_encryption_valueerror_leaks_detail(self, app)`
- `test_encryption_valueerror_fixed_no_detail` (line 159) `def test_encryption_valueerror_fixed_no_detail(self, app)`
- `test_encryption_runtimeerror_fixed_no_detail` (line 167) `def test_encryption_runtimeerror_fixed_no_detail(self, app)`
- `app` (line 180) `def app(self)`
- `test_source_delete_keyerror_fixed` (line 187) `def test_source_delete_keyerror_fixed(self, app)`
- `test_source_create_valueerror_fixed` (line 208) `def test_source_create_valueerror_fixed(self, app)`
- `test_source_update_valueerror_fixed` (line 232) `def test_source_update_valueerror_fixed(self, app)`
- `app` (line 264) `def app(self)`
- `test_redirect_uses_public_host_not_request_host` (line 271) `def test_redirect_uses_public_host_not_request_host(self, app)`
- `test_redirect_scheme_is_https` (line 285) `def test_redirect_scheme_is_https(self, app)`
- `test_ci_yml_has_permissions` (line 309) `def test_ci_yml_has_permissions(self)`
- `test_ci_yml_permissions_is_read_all` (line 325) `def test_ci_yml_permissions_is_read_all(self)`
- `app` (line 340) `def app(self)`
- `_make_osiris_route_fixed` (line 347) `def _make_osiris_route_fixed(self, app, route_path)`
- `test_osiris_exception_returns_generic` (line 386) `def test_osiris_exception_returns_generic(self, app)`
- `test_redirect_implementation_uses_public_host` (line 402) `def test_redirect_implementation_uses_public_host(self)`
- `test_source_has_no_url_replace` (line 418) `def test_source_has_no_url_replace(self)`
- `test_js_file_exists` (line 436) `def test_js_file_exists(self)`
- `test_innerhtml_not_used_with_template_literals` (line 439) `def test_innerhtml_not_used_with_template_literals(self)`
- `test_showtooltipat_safe` (line 459) `def test_showtooltipat_safe(self)` - *showTooltipAt must sanitize html before innerHTML assignment.*
- `test_selectnode_inspector_safe` (line 474) `def test_selectnode_inspector_safe(self)` - *selectNode must build DOM safely for the inspector panel.*
- `test_refuses_link_local_metadata` (line 503) `def test_refuses_link_local_metadata(self)`
- `test_refuses_loopback` (line 508) `def test_refuses_loopback(self)`
- `test_refuses_disallowed_scheme` (line 512) `def test_refuses_disallowed_scheme(self)`
- `test_user_channel_url_cannot_reach_internal_host` (line 516) `def test_user_channel_url_cannot_reach_internal_host(self)`
- `test_raw_channel_url_refused_even_for_safe_host` (line 523) `def test_raw_channel_url_refused_even_for_safe_host(self)`
- `test_redirect_handler_refuses` (line 542) `def test_redirect_handler_refuses(self)`
- `test_http_post_does_not_follow_redirect` (line 547) `def test_http_post_does_not_follow_redirect(self)`
- `test_no_insert_adjacent_html_in_tooltip` (line 601) `def test_no_insert_adjacent_html_in_tooltip(self)`
- `test_sanitizer_blocks_dangerous_schemes_and_style` (line 618) `def test_sanitizer_blocks_dangerous_schemes_and_style(self)`
- `test_no_innerhtml_markdown_sink` (line 630) `def test_no_innerhtml_markdown_sink(self)`
- `test_no_html_string_round_trip` (line 636) `def test_no_html_string_round_trip(self)`
- `test_vendored_dompurify_wired_with_fallback` (line 643) `def test_vendored_dompurify_wired_with_fallback(self)`
- `api_export_test` (line 97) `def api_export_test()`
- `api_export_fixed` (line 130) `def api_export_fixed()`
- `api_delete` (line 191) `def api_delete(name)`
- `api_create` (line 212) `def api_create()`
- `api_update` (line 236) `def api_update(name)`
- `fetch_bgp` (line 354) `def fetch_bgp(q)`
- `fetch_mac` (line 357) `def fetch_mac(mac)`
- `fetch_phone` (line 360) `def fetch_phone(n)`
- `fetch_github_user` (line 363) `def fetch_github_user(u)`
- `fetch_leaks` (line 366) `def fetch_leaks(e)`
- `osiris_endpoint` (line 376) `def osiris_endpoint()`
- `_fake_build_opener` (line 571) `def _fake_build_opener()`
- `__enter__` (line 558) `def __enter__(self)`
- `__exit__` (line 561) `def __exit__(self)`
- `open` (line 565) `def open(self, req, timeout)`

#### `test_socmint.py`
**Path:** `tests/test_socmint.py`
**File Doc:** *BDD tests for SOCMINT sources and SocialMediaInferer.  Covers: - S1: YouTube channel lookup (happy path) - S2: YouTube channel not found - S3: YouTube without API key - S4: Twitch user lookup (happy path) - S5: Twitch user not found - S6: Twitter/X user lookup (happy path) - S7: Discord server search (happy path) - S8: SocialMediaInferer correlates across platforms - S9: SocialMediaInferer handles unknown username - S10: YouTube parser handles malformed response - S11: Twitch parser handles error responses - S12: Entity extraction from social media profiles*

**Classes:**
- `TestS1YouTubeHappyPath` (line 144) `class TestS1YouTubeHappyPath` - *S1: YouTube channel lookup returns structured profile.*
- `TestS2YouTubeNotFound` (line 174) `class TestS2YouTubeNotFound` - *S2: YouTube channel not found returns error.*
- `TestS3YouTubeRequiresKey` (line 193) `class TestS3YouTubeRequiresKey` - *S3: YouTube source requires a key and is excluded when --include-paid is off.*
- `TestS4TwitchHappyPath` (line 222) `class TestS4TwitchHappyPath` - *S4: Twitch user lookup returns structured profile.*
- `TestS5TwitchNotFound` (line 250) `class TestS5TwitchNotFound` - *S5: Twitch user not found returns error.*
- `TestS6TwitterHappyPath` (line 279) `class TestS6TwitterHappyPath` - *S6: Twitter/X user lookup returns structured profile.*
- `TestS7DiscordHappyPath` (line 319) `class TestS7DiscordHappyPath` - *S7: Discord server search returns structured results.*
- `TestS8InfererCrossPlatform` (line 358) `class TestS8InfererCrossPlatform` - *S8: SocialMediaInferer correlates usernames across platforms.*
- `TestS9InfererUnknown` (line 406) `class TestS9InfererUnknown` - *S9: SocialMediaInferer handles unknown/empty usernames.*
- `TestS10YouTubeMalformed` (line 442) `class TestS10YouTubeMalformed` - *S10: YouTube parser handles malformed inputs.*
- `TestS11TwitchErrors` (line 479) `class TestS11TwitchErrors` - *S11: Twitch parser handles error responses.*
- `TestS12EntityExtraction` (line 507) `class TestS12EntityExtraction` - *S12: Entity extraction works on SOCMINT parser outputs.*
- `TestInfererPlatformList` (line 574) `class TestInfererPlatformList` - *Verify the platform list method.*
- `TestInfererResolveSpecificPlatforms` (line 593) `class TestInfererResolveSpecificPlatforms` - *Verify filtering by specific platforms.*
- `TestParserTotalness` (line 622) `class TestParserTotalness` - *Every parser must be total: any unrecognised input returns empty, not raises.*

**Functions:**
- `youtube_response` (line 40) `def youtube_response()` - *Simulated YouTube Data API v3 response for @mkbhd.*
- `twitch_response` (line 73) `def twitch_response()` - *Simulated Twitch Helix API response for shroud.*
- `twitter_response` (line 92) `def twitter_response()` - *Simulated Twitter/X API v2 response for elonmusk.*
- `discord_response` (line 117) `def discord_response()` - *Simulated discords.com API search response.*

**Methods:**
- `test_parser_returns_channel_id` (line 147) `def test_parser_returns_channel_id(self, youtube_response)` - *Given a valid YouTube channel response, the parser returns the channel_id.*
- `test_parser_returns_subscriber_count` (line 153) `def test_parser_returns_subscriber_count(self, youtube_response)` - *Given a valid YouTube channel response, the parser returns subscriber_count.*
- `test_parser_returns_metadata` (line 160) `def test_parser_returns_metadata(self, youtube_response)` - *Given a valid YouTube channel response, the parser returns title and description.*
- `test_empty_items_returns_not_found` (line 177) `def test_empty_items_returns_not_found(self)` - *Given an empty items list, the parser returns not_found.*
- `test_missing_items_returns_not_found` (line 182) `def test_missing_items_returns_not_found(self)` - *Given a response with no items key, the parser returns not_found.*
- `test_yaml_source_has_requires_key` (line 196) `def test_yaml_source_has_requires_key(self)` - *Given the youtube_user YAML source, requires_key is True.*
- `test_parser_registered` (line 209) `def test_parser_registered(self)` - *Given the youtube_user source, its parser is registered.*
- `test_parser_returns_user_id` (line 225) `def test_parser_returns_user_id(self, twitch_response)` - *Given a valid Twitch response, the parser returns the user id.*
- `test_parser_returns_display_name` (line 231) `def test_parser_returns_display_name(self, twitch_response)` - *Given a valid Twitch response, the parser returns display_name.*
- `test_parser_returns_metadata` (line 237) `def test_parser_returns_metadata(self, twitch_response)` - *Given a valid Twitch response, the parser returns type and view_count.*
- `test_empty_data_returns_not_found` (line 253) `def test_empty_data_returns_not_found(self)` - *Given an empty data list, the parser returns not_found.*
- `test_error_response_returns_api_error` (line 258) `def test_error_response_returns_api_error(self)` - *Given an error response, the parser returns api_error with the detail message.*
- `test_missing_data_returns_not_found` (line 268) `def test_missing_data_returns_not_found(self)` - *Given a response with no data key, the parser returns not_found.*
- `test_parser_returns_username` (line 282) `def test_parser_returns_username(self, twitter_response)` - *Given a valid Twitter response, the parser returns username.*
- `test_parser_returns_followers_count` (line 288) `def test_parser_returns_followers_count(self, twitter_response)` - *Given a valid Twitter response, the parser returns followers_count.*
- `test_parser_returns_verified_flag` (line 294) `def test_parser_returns_verified_flag(self, twitter_response)` - *Given a valid Twitter response, the parser returns the verified flag.*
- `test_parser_returns_metadata` (line 299) `def test_parser_returns_metadata(self, twitter_response)` - *Given a valid Twitter response, the parser returns location and description.*
- `test_not_found_with_errors` (line 306) `def test_not_found_with_errors(self)` - *Given an error response with errors list, returns not_found.*
- `test_parser_returns_server_list` (line 322) `def test_parser_returns_server_list(self, discord_response)` - *Given a Discord server search response, the parser returns server list.*
- `test_parser_returns_server_names` (line 328) `def test_parser_returns_server_names(self, discord_response)` - *Given a Discord server search response, server names are correct.*
- `test_parser_returns_member_counts` (line 334) `def test_parser_returns_member_counts(self, discord_response)` - *Given a Discord server search response, member counts are integers.*
- `test_empty_response` (line 341) `def test_empty_response(self)` - *Given an empty response, the parser returns empty results.*
- `test_none_response` (line 347) `def test_none_response(self)` - *Given a None response, the parser returns empty results.*
- `test_resolve_torvalds` (line 361) `def test_resolve_torvalds(self)` - *Given username 'torvalds', the inferer returns profiles across platforms.*
- `test_resolve_includes_keybase` (line 367) `def test_resolve_includes_keybase(self)` - *Given a username, Keybase appears in platform profiles.*
- `test_resolve_includes_github` (line 373) `def test_resolve_includes_github(self)` - *Given a username, GitHub appears in platform profiles.*
- `test_resolve_has_high_confidence_for_populated_username` (line 379) `def test_resolve_has_high_confidence_for_populated_username(self)` - *Given a common username, cross-platform confidence is above 0.5.*
- `test_resolve_linked_platforms_contains_keybase_note` (line 386) `def test_resolve_linked_platforms_contains_keybase_note(self)` - *Given a username, linked_platforms shows the Keybase proof chain.*
- `test_resolve_has_profile_urls` (line 393) `def test_resolve_has_profile_urls(self)` - *Given a username, each platform profile has a profile_url.*
- `test_empty_username` (line 409) `def test_empty_username(self)` - *Given an empty username, returns no_matches.*
- `test_none_username` (line 414) `def test_none_username(self)` - *Given None as username, returns no_matches.*
- `test_always_has_profile_count` (line 419) `def test_always_has_profile_count(self)` - *Given any username, total_platforms is the count of known platforms.*
- `test_platform_urls_are_valid` (line 427) `def test_platform_urls_are_valid(self)` - *Given any username, all profile URLs are valid templates.*
- `test_none_input` (line 445) `def test_none_input(self)` - *Given None input, returns error.*
- `test_list_input` (line 450) `def test_list_input(self)` - *Given a list instead of dict, returns error.*
- `test_string_input` (line 455) `def test_string_input(self)` - *Given a string instead of dict, returns error.*
- `test_missing_statistics` (line 460) `def test_missing_statistics(self)` - *Given a response without statistics, parser returns defaults gracefully.*
- `test_401_error` (line 482) `def test_401_error(self)` - *Given a 401 error response, returns api_error.*
- `test_none_input` (line 491) `def test_none_input(self)` - *Given None input, returns error.*
- `test_list_input` (line 496) `def test_list_input(self)` - *Given a list instead of dict, returns error.*
- `test_youtube_profile_extracts_person` (line 510) `def test_youtube_profile_extracts_person(self)` - *Given a YouTube channel with display_name, extract_structured yields a person.*
- `test_twitter_profile_extracts_person_and_username` (line 528) `def test_twitter_profile_extracts_person_and_username(self)` - *Given a Twitter parser output, extract_structured yields person/username entities.*
- `test_social_media_urls_in_text` (line 548) `def test_social_media_urls_in_text(self)` - *Given text containing social URLs, the inferer discovers cross-platform links.*
- `test_discover_empty_text` (line 558) `def test_discover_empty_text(self)` - *Given empty text, discover_from_text returns empty.*
- `test_discover_no_urls` (line 563) `def test_discover_no_urls(self)` - *Given text without URLs, discover_from_text returns empty.*
- `test_platform_list_returns_all` (line 577) `def test_platform_list_returns_all(self)` - *Given the inferer, platform_list returns all known platforms.*
- `test_platform_list_has_required_fields` (line 583) `def test_platform_list_has_required_fields(self)` - *Given the platform list, every entry has the core fields.*
- `test_resolve_single_platform` (line 596) `def test_resolve_single_platform(self)` - *Given a specific platform filter, only that platform appears.*
- `test_resolve_multiple_platforms` (line 602) `def test_resolve_multiple_platforms(self)` - *Given a list of platforms, only those platforms appear.*
- `test_resolve_validates_twitter_requires_key` (line 609) `def test_resolve_validates_twitter_requires_key(self)` - *Given the inferer resolve, Twitter profiles are marked as requiring key.*
- `test_parser_handles_none` (line 631) `def test_parser_handles_none(self, parser_fn)` - *Given None input, the parser does not raise.*
- `test_parser_handles_int` (line 645) `def test_parser_handles_int(self, parser_fn)` - *Given int input, the parser does not raise.*
- `test_parser_handles_string` (line 659) `def test_parser_handles_string(self, parser_fn)` - *Given string input, the parser does not raise.*

#### `test_source_health_monitoring.py`
**Path:** `tests/test_source_health_monitoring.py`
**File Doc:** *BDD tests for estorides_core.source_health_monitoring.  These tests implement the Given-When-Then contracts declared in spec/source_health_monitoring.md. They must fail against the unwritten implementation (red step) and pass after the green step.*

**Classes:**
- `TestHealthySource` (line 29) `class TestHealthySource` - *H1: high success rate, low latency -> HEALTHY.*
- `TestDegradingLowSuccess` (line 97) `class TestDegradingLowSuccess` - *H2: low success rate -> DEGRADING.*
- `TestDegradingHighLatency` (line 141) `class TestDegradingHighLatency` - *H3: high latency -> DEGRADING.*
- `TestStaleSource` (line 186) `class TestStaleSource` - *H4: long since last seen -> STALE.*
- `TestUnknownSource` (line 217) `class TestUnknownSource` - *H5: fetch_count < min_fetches -> UNKNOWN.*
- `TestDashboard` (line 248) `class TestDashboard` - *H6: dashboard filters by status.*
- `TestValidation` (line 348) `class TestValidation` - *H7: invalid inputs raise ValueError.*
- `TestDeterminism` (line 411) `class TestDeterminism` - *H8: same input -> same output.*
- `TestScoreBounded` (line 441) `class TestScoreBounded` - *H9 smoke: health_score is always in [0, 1].*
- `TestStatusAlwaysValid` (line 472) `class TestStatusAlwaysValid` - *H10: status is always a valid enum member.*
- `TestDataclassContract` (line 492) `class TestDataclassContract` - *All public types are frozen dataclasses with to_dict.*

**Methods:**
- `test_healthy_status` (line 32) `def test_healthy_status(self)`
- `test_success_rate_computed` (line 44) `def test_success_rate_computed(self)`
- `test_avg_latency_computed` (line 56) `def test_avg_latency_computed(self)`
- `test_freshness_hours_computed` (line 68) `def test_freshness_hours_computed(self)`
- `test_health_score_high_band` (line 80) `def test_health_score_high_band(self)`
- `test_degrading_status` (line 100) `def test_degrading_status(self)`
- `test_success_rate_reflects_failures` (line 112) `def test_success_rate_reflects_failures(self)`
- `test_health_score_low_band` (line 124) `def test_health_score_low_band(self)`
- `test_degrading_status_for_latency` (line 144) `def test_degrading_status_for_latency(self)`
- `test_avg_latency_high` (line 156) `def test_avg_latency_high(self)`
- `test_health_score_penalised` (line 168) `def test_health_score_penalised(self)`
- `test_stale_status` (line 189) `def test_stale_status(self)`
- `test_freshness_hours_exceeds_stale` (line 201) `def test_freshness_hours_exceeds_stale(self)`
- `test_unknown_status` (line 220) `def test_unknown_status(self)`
- `test_zero_fetches_is_unknown` (line 232) `def test_zero_fetches_is_unknown(self)`
- `_healthy` (line 252) `def _healthy(name)`
- `_degrading` (line 263) `def _degrading(name)`
- `_stale` (line 274) `def _stale(name)`
- `_unknown` (line 285) `def _unknown(name)`
- `test_hot_sources_are_healthy` (line 295) `def test_hot_sources_are_healthy(self)`
- `test_degrading_includes_degrading_and_stale` (line 306) `def test_degrading_includes_degrading_and_stale(self)`
- `test_unknown_sources_separate` (line 319) `def test_unknown_sources_separate(self)`
- `test_summary_counts` (line 329) `def test_summary_counts(self)`
- `test_ok_exceeds_fetch_raises` (line 351) `def test_ok_exceeds_fetch_raises(self)`
- `test_negative_fetch_raises` (line 362) `def test_negative_fetch_raises(self)`
- `test_negative_latency_raises` (line 373) `def test_negative_latency_raises(self)`
- `test_empty_name_raises` (line 384) `def test_empty_name_raises(self)`
- `test_config_min_fetches_less_than_one_raises` (line 395) `def test_config_min_fetches_less_than_one_raises(self)`
- `test_config_stale_hours_zero_raises` (line 399) `def test_config_stale_hours_zero_raises(self)`
- `test_config_degrading_rate_out_of_range_raises` (line 403) `def test_config_degrading_rate_out_of_range_raises(self)`
- `test_compute_health_is_pure` (line 414) `def test_compute_health_is_pure(self)`
- `test_build_dashboard_is_pure` (line 428) `def test_build_dashboard_is_pure(self)`
- `test_perfect_source_scores_one` (line 444) `def test_perfect_source_scores_one(self)`
- `test_broken_source_scores_low` (line 456) `def test_broken_source_scores_low(self)`
- `test_status_is_enum` (line 475) `def test_status_is_enum(self)`
- `test_health_input_is_dataclass` (line 495) `def test_health_input_is_dataclass(self)`
- `test_health_result_is_dataclass` (line 498) `def test_health_result_is_dataclass(self)`
- `test_config_is_dataclass` (line 501) `def test_config_is_dataclass(self)`
- `test_dashboard_is_dataclass` (line 505) `def test_dashboard_is_dataclass(self)`
- `test_result_to_dict` (line 508) `def test_result_to_dict(self)`
- `test_dashboard_to_dict` (line 523) `def test_dashboard_to_dict(self)`

#### `test_source_loader.py`
**Path:** `tests/test_source_loader.py`
**File Doc:** *BDD / regression tests for SourceRegistry loading.  - SL1: a multi-document YAML file loads every document - SL2: a document holding a list of sources loads every entry - SL3: a duplicate name overwrites cleanly and category counts stay consistent - SL4: an unknown `contact` value is normalised to `active` - SL5: an undecodable file is skipped without aborting the load*

**Classes:**
- `TestSL1MultiDocument` (line 32) `class TestSL1MultiDocument`
- `TestSL2ListDocument` (line 42) `class TestSL2ListDocument`
- `TestSL3DuplicateName` (line 55) `class TestSL3DuplicateName`
- `TestSL4UnknownContact` (line 70) `class TestSL4UnknownContact`
- `TestSL5UndecodableFile` (line 78) `class TestSL5UndecodableFile`

**Functions:**
- `_write` (line 17) `def _write(path, text)`
- `_source` (line 22) `def _source(name, category, extra)`

**Methods:**
- `test_two_documents_load` (line 33) `def test_two_documents_load(self, tmp_path)`
- `test_list_of_sources_loads` (line 43) `def test_list_of_sources_loads(self, tmp_path)`
- `test_duplicate_overwrites_counts` (line 56) `def test_duplicate_overwrites_counts(self, tmp_path)`
- `test_unknown_contact_becomes_active` (line 71) `def test_unknown_contact_becomes_active(self, tmp_path)`
- `test_bad_encoding_does_not_abort_load` (line 79) `def test_bad_encoding_does_not_abort_load(self, tmp_path)`

#### `test_source_routing.py`
**Path:** `tests/test_source_routing.py`
**File Doc:** *M2 RED tests: alias routing + phone/mac YAML backfill.*

**Functions:**
- `_names` (line 13) `def _names(types)`
- `test_phone_routes_to_phone_tools` (line 26) `def test_phone_routes_to_phone_tools()`
- `test_mac_routes_to_macvendors` (line 32) `def test_mac_routes_to_macvendors()`
- `test_hash_alias_matches_concrete` (line 36) `def test_hash_alias_matches_concrete()`
- `test_url_also_matches_domain_sources` (line 43) `def test_url_also_matches_domain_sources()`
- `test_phone_yaml_tags` (line 47) `def test_phone_yaml_tags()`

#### `test_sqlite_store.py`
**Path:** `tests/test_sqlite_store.py`
**File Doc:** *BDD tests for the shared SQLite store base (spec/sqlite_store.md).  - SS1: schema applied, default path honoured - SS2: `_tx` commits - SS3: `_tx` rolls back and re-raises - SS4: `close` is idempotent - SS5: DictMixin maps a dataclass to a dict*

**Classes:**
- `_Store` (line 20) `class _Store(SqliteStore)`
- `TestSS1Schema` (line 24) `class TestSS1Schema`
- `TestSS2Commit` (line 43) `class TestSS2Commit`
- `TestSS3Rollback` (line 54) `class TestSS3Rollback`
- `TestSS4Close` (line 67) `class TestSS4Close`
- `TestSS5DictMixin` (line 74) `class TestSS5DictMixin`
- `NoPath` (line 36) `class NoPath(SqliteStore)`
- `Row` (line 77) `class Row(DictMixin)`

**Methods:**
- `test_default_path_and_schema` (line 25) `def test_default_path_and_schema(self, tmp_path)`
- `test_missing_path_raises` (line 35) `def test_missing_path_raises(self)`
- `test_tx_commits` (line 44) `def test_tx_commits(self, tmp_path)`
- `test_tx_rolls_back_and_reraises` (line 55) `def test_tx_rolls_back_and_reraises(self, tmp_path)`
- `test_close_idempotent` (line 68) `def test_close_idempotent(self, tmp_path)`
- `test_to_dict` (line 75) `def test_to_dict(self)`

#### `test_structured_extraction.py`
**Path:** `tests/test_structured_extraction.py`
**File Doc:** *BDD tests for structured human-selector extraction + pivot leaf surfacing.  Ported from the standalone ``_test_people.py`` validator into pytest. Offline.*

**Classes:**
- `TestExtractStructured` (line 39) `class TestExtractStructured`
- `_StubRunner` (line 58) `class _StubRunner`
- `TestPivotLeafSurfacing` (line 67) `class TestPivotLeafSurfacing`

**Functions:**
- `_types` (line 32) `def _types(payload)`

**Methods:**
- `test_happy_path_selectors` (line 40) `def test_happy_path_selectors(self)`
- `test_noise_rejected` (line 51) `def test_noise_rejected(self)`
- `run` (line 59) `def run(self, query)`
- `test_non_pivotable_selectors_surface_as_leaves` (line 68) `def test_non_pivotable_selectors_surface_as_leaves(self)`

#### `test_supply_chain.py`
**Path:** `tests/test_supply_chain.py`
**File Doc:** *ATDD + BDD tests for estorides_core.supply_chain.  Implements the Given-When-Then contracts declared in ``spec/supply_chain.md``.*

**Classes:**
- `TestCDNDetection` (line 18) `class TestCDNDetection`
- `TestEmailProviderDetection` (line 36) `class TestEmailProviderDetection`
- `TestNoThirdParties` (line 66) `class TestNoThirdParties`
- `TestSharedASN` (line 74) `class TestSharedASN`
- `TestNoOutboundScanning` (line 95) `class TestNoOutboundScanning`
- `TestSubsidiaryDetection` (line 113) `class TestSubsidiaryDetection`
- `TestCommonIssuerExcluded` (line 133) `class TestCommonIssuerExcluded`
- `TestRegistrarDetection` (line 148) `class TestRegistrarDetection`

**Methods:**
- `test_cloudflare_cdn_detected` (line 19) `def test_cloudflare_cdn_detected(self)`
- `test_google_workspace_mx_detected` (line 37) `def test_google_workspace_mx_detected(self)`
- `test_microsoft_365_mx_detected` (line 51) `def test_microsoft_365_mx_detected(self)`
- `test_empty_when_self_hosted` (line 67) `def test_empty_when_self_hosted(self)`
- `test_asn_sharing_detected` (line 75) `def test_asn_sharing_detected(self)`
- `test_no_http_to_third_parties` (line 96) `def test_no_http_to_third_parties(self)`
- `test_subsidiary_relationship` (line 114) `def test_subsidiary_relationship(self)`
- `test_lets_encrypt_not_flagged` (line 134) `def test_lets_encrypt_not_flagged(self)`
- `test_godaddy_registrar` (line 149) `def test_godaddy_registrar(self)`

#### `test_system_app_sources.py`
**Path:** `tests/test_system_app_sources.py`
**File Doc:** *BDD tests for estorides_core.system_app_sources.  Kali OSINT CLI tools as first-class `system_app` sources: same aggregation pipeline as HTTP sources, new YAML origin kind. Scenarios mirror `spec/system_app_sources.md` (S1-S8).*

**Classes:**
- `TestS1HappyPath` (line 79) `class TestS1HappyPath`
- `TestS2MissingBinary` (line 119) `class TestS2MissingBinary`
- `TestS3Crash` (line 151) `class TestS3Crash`
- `TestS4Injection` (line 171) `class TestS4Injection`
- `TestS5JsonFileOutput` (line 190) `class TestS5JsonFileOutput`
- `TestS6Placeholders` (line 254) `class TestS6Placeholders`
- `TestS7ContactCeiling` (line 272) `class TestS7ContactCeiling`
- `TestS8Registry` (line 306) `class TestS8Registry`
- `TestRunnerErrorPassthrough` (line 408) `class TestRunnerErrorPassthrough`
- `TestS9LoopResponsiveness` (line 433) `class TestS9LoopResponsiveness`

**Functions:**
- `_stub_runner` (line 48) `def _stub_runner(exit_code, stdout, stderr, error_code, error_message, on_run)`

**Methods:**
- `stub` (line 52) `def stub(binary, args)`
- `test_execute_renders_query_and_parses_found_lines` (line 80) `def test_execute_renders_query_and_parses_found_lines(self)`
- `test_execute_returns_source_and_tool_metadata` (line 105) `def test_execute_returns_source_and_tool_metadata(self)`
- `test_execute_reports_tool_not_found` (line 120) `def test_execute_reports_tool_not_found(self, monkeypatch)`
- `test_execute_reports_missing_binary_declaration` (line 130) `def test_execute_reports_missing_binary_declaration(self)`
- `test_execute_rejects_non_allowlisted_binary` (line 136) `def test_execute_rejects_non_allowlisted_binary(self, monkeypatch)`
- `test_nonzero_exit_keeps_parsed_output` (line 152) `def test_nonzero_exit_keeps_parsed_output(self)`
- `test_metachar_arg_is_rejected_by_tool_runner` (line 172) `def test_metachar_arg_is_rejected_by_tool_runner(self, monkeypatch)`
- `test_file_output_is_parsed_and_outdir_cleaned` (line 208) `def test_file_output_is_parsed_and_outdir_cleaned(self, monkeypatch)`
- `test_stdout_json_is_parsed_when_no_file` (line 235) `def test_stdout_json_is_parsed_when_no_file(self, monkeypatch)`
- `test_query_and_outdir_substituted` (line 255) `def test_query_and_outdir_substituted(self)`
- `test_unknown_tokens_survive` (line 259) `def test_unknown_tokens_survive(self)`
- `test_non_string_arg_raises` (line 263) `def test_non_string_arg_raises(self)`
- `test_passive_only_drops_touching_tools_even_by_name` (line 273) `def test_passive_only_drops_touching_tools_even_by_name(self)`
- `_load` (line 307) `def _load(self, tmp_path, yaml_text)`
- `test_kind_and_output_format_normalise` (line 329) `def test_kind_and_output_format_normalise(self, tmp_path)`
- `test_kind_derived_from_binary_when_omitted` (line 336) `def test_kind_derived_from_binary_when_omitted(self, tmp_path)`
- `test_http_source_gets_http_kind_by_default` (line 341) `def test_http_source_gets_http_kind_by_default(self, tmp_path)`
- `test_bad_output_format_falls_back_to_text` (line 352) `def test_bad_output_format_falls_back_to_text(self, tmp_path)`
- `test_non_string_args_reset` (line 359) `def test_non_string_args_reset(self, tmp_path)`
- `test_unknown_kind_derives_from_block` (line 366) `def test_unknown_kind_derives_from_block(self, tmp_path)`
- `test_summary_exposes_kind` (line 371) `def test_summary_exposes_kind(self, tmp_path)`
- `test_real_kali_yamls_load_as_system_app` (line 376) `def test_real_kali_yamls_load_as_system_app(self)`
- `test_write_source_file_roundtrip_keeps_system_app_block` (line 395) `def test_write_source_file_roundtrip_keeps_system_app_block(self, tmp_path)`
- `test_timeout_error_is_propagated` (line 409) `def test_timeout_error_is_propagated(self, monkeypatch)`
- `test_binary_branch_runs_in_worker_thread` (line 434) `def test_binary_branch_runs_in_worker_thread(self, monkeypatch)`
- `on_run` (line 83) `def on_run(binary, args)`
- `on_run` (line 214) `def on_run(binary, args)`
- `timeout_runner` (line 414) `def timeout_runner(binary, args)`
- `slow_execute` (line 444) `def slow_execute(source, query)`
- `scenario` (line 463) `def scenario()`

#### `test_target_management.py`
**Path:** `tests/test_target_management.py`

**Classes:**
- `TestS1HappyPath` (line 21) `class TestS1HappyPath`
- `TestS2AutoDetect` (line 46) `class TestS2AutoDetect`
- `TestS3InvalidValue` (line 113) `class TestS3InvalidValue`
- `TestS4UnknownType` (line 140) `class TestS4UnknownType`
- `TestS5EmptyValue` (line 156) `class TestS5EmptyValue`
- `TestS6CaseStoreUnavailable` (line 174) `class TestS6CaseStoreUnavailable`
- `TestS7Batch` (line 186) `class TestS7Batch`
- `TestS8MaxBatch` (line 211) `class TestS8MaxBatch`
- `TestS9XSS` (line 228) `class TestS9XSS`
- `TestS10Determinism` (line 246) `class TestS10Determinism`
- `TestValidateType` (line 266) `class TestValidateType`
- `TestValidateValue` (line 280) `class TestValidateValue`
- `TestMakeTargetId` (line 333) `class TestMakeTargetId`
- `TestAutoDetectType` (line 344) `class TestAutoDetectType`
- `TestBatchResultSerialization` (line 367) `class TestBatchResultSerialization`
- `TestTargetResultSerialization` (line 379) `class TestTargetResultSerialization`
- `TestCsvImport` (line 410) `class TestCsvImport`

**Methods:**
- `test_valid_target_returns_result` (line 22) `def test_valid_target_returns_result(self)`
- `test_has_case_id` (line 30) `def test_has_case_id(self)`
- `test_id_deterministic` (line 35) `def test_id_deterministic(self)`
- `test_ipv4_auto` (line 47) `def test_ipv4_auto(self)`
- `test_ipv6_auto` (line 52) `def test_ipv6_auto(self)`
- `test_email_auto` (line 57) `def test_email_auto(self)`
- `test_cve_auto` (line 62) `def test_cve_auto(self)`
- `test_btc_auto` (line 67) `def test_btc_auto(self)`
- `test_eth_auto` (line 72) `def test_eth_auto(self)`
- `test_domain_auto` (line 77) `def test_domain_auto(self)`
- `test_username_fallback` (line 82) `def test_username_fallback(self)`
- `test_phone_auto` (line 87) `def test_phone_auto(self)`
- `test_asn_auto` (line 92) `def test_asn_auto(self)`
- `test_md5_auto` (line 97) `def test_md5_auto(self)`
- `test_sha256_auto` (line 102) `def test_sha256_auto(self)`
- `test_invalid_email` (line 114) `def test_invalid_email(self)`
- `test_invalid_ipv4` (line 119) `def test_invalid_ipv4(self)`
- `test_invalid_domain_script` (line 123) `def test_invalid_domain_script(self)`
- `test_invalid_url` (line 127) `def test_invalid_url(self)`
- `test_invalid_phone` (line 131) `def test_invalid_phone(self)`
- `test_unknown_type` (line 141) `def test_unknown_type(self)`
- `test_unknown_type_via_manager` (line 146) `def test_unknown_type_via_manager(self)`
- `test_empty_raises` (line 157) `def test_empty_raises(self)`
- `test_whitespace_raises` (line 161) `def test_whitespace_raises(self)`
- `test_validate_value_empty` (line 165) `def test_validate_value_empty(self)`
- `test_ephemeral_no_case_store` (line 175) `def test_ephemeral_no_case_store(self)`
- `test_mixed_batch` (line 187) `def test_mixed_batch(self)`
- `test_simple_lines_no_type` (line 201) `def test_simple_lines_no_type(self)`
- `test_exceeds_max` (line 212) `def test_exceeds_max(self)`
- `test_at_max` (line 217) `def test_at_max(self)`
- `test_script_in_domain_rejected` (line 229) `def test_script_in_domain_rejected(self)`
- `test_onclick_in_domain_rejected` (line 233) `def test_onclick_in_domain_rejected(self)`
- `test_sql_injection_in_email_rejected` (line 237) `def test_sql_injection_in_email_rejected(self)`
- `test_same_id` (line 247) `def test_same_id(self)`
- `test_same_id_normalised` (line 255) `def test_same_id_normalised(self)`
- `test_valid_types_pass` (line 267) `def test_valid_types_pass(self)`
- `test_auto_passes` (line 272) `def test_auto_passes(self)`
- `test_invalid_fails` (line 275) `def test_invalid_fails(self)`
- `test_domain_valid` (line 281) `def test_domain_valid(self)`
- `test_domain_invalid` (line 285) `def test_domain_invalid(self)`
- `test_ipv4_valid` (line 289) `def test_ipv4_valid(self)`
- `test_ipv4_invalid_octet` (line 293) `def test_ipv4_invalid_octet(self)`
- `test_email_valid` (line 297) `def test_email_valid(self)`
- `test_url_only_http_https` (line 301) `def test_url_only_http_https(self)`
- `test_username_no_regex` (line 306) `def test_username_no_regex(self)`
- `test_cve_valid` (line 310) `def test_cve_valid(self)`
- `test_btc_valid` (line 313) `def test_btc_valid(self)`
- `test_eth_valid` (line 316) `def test_eth_valid(self)`
- `test_phone_valid` (line 319) `def test_phone_valid(self)`
- `test_asn_valid` (line 322) `def test_asn_valid(self)`
- `test_md5_valid` (line 325) `def test_md5_valid(self)`
- `test_sha256_valid` (line 328) `def test_sha256_valid(self)`
- `test_length` (line 334) `def test_length(self)`
- `test_deterministic` (line 337) `def test_deterministic(self)`
- `test_case_sensitive_normalised` (line 340) `def test_case_sensitive_normalised(self)`
- `test_ipv4` (line 345) `def test_ipv4(self)`
- `test_ipv6` (line 348) `def test_ipv6(self)`
- `test_email` (line 351) `def test_email(self)`
- `test_domain` (line 354) `def test_domain(self)`
- `test_url` (line 357) `def test_url(self)`
- `test_cve` (line 360) `def test_cve(self)`
- `test_username_fallback` (line 363) `def test_username_fallback(self)`
- `test_to_dict` (line 368) `def test_to_dict(self)`
- `test_to_dict` (line 380) `def test_to_dict(self)`
- `test_to_dict_invalid` (line 397) `def test_to_dict_invalid(self)`
- `test_basic_csv` (line 411) `def test_basic_csv(self)`
- `test_csv_invalid` (line 418) `def test_csv_invalid(self)`
- `test_csv_max_batch` (line 425) `def test_csv_max_batch(self)`
- `test_csv_exceeds_max` (line 431) `def test_csv_exceeds_max(self)`

#### `test_target_scoring.py`
**Path:** `tests/test_target_scoring.py`
**File Doc:** *ATDD + BDD tests for estorides_core.target_scoring.  Implements the Given-When-Then contracts declared in ``spec/target_scoring.md``.*

**Classes:**
- `TestCriticalTarget` (line 36) `class TestCriticalTarget`
- `TestNoFindingsNoise` (line 44) `class TestNoFindingsNoise`
- `TestMixedScoring` (line 52) `class TestMixedScoring`
- `TestConfigurableWeights` (line 70) `class TestConfigurableWeights`
- `TestCrownJewelDetection` (line 82) `class TestCrownJewelDetection`
- `TestPartialData` (line 94) `class TestPartialData`
- `TestLateralPotential` (line 101) `class TestLateralPotential`
- `TestNoCredentialLeakage` (line 108) `class TestNoCredentialLeakage`

**Functions:**
- `_make_target` (line 17) `def _make_target(domain, surface, soft, jewel, lateral)`

**Methods:**
- `test_open_bucket_and_old_nginx_ranked_critical` (line 37) `def test_open_bucket_and_old_nginx_ranked_critical(self)`
- `test_no_findings_is_noise` (line 45) `def test_no_findings_is_noise(self)`
- `test_5_targets_various_tiers` (line 53) `def test_5_targets_various_tiers(self)`
- `test_custom_weights_change_score` (line 71) `def test_custom_weights_change_score(self)`
- `test_jenkins_jira_vpn_get_high_jewel_score` (line 83) `def test_jenkins_jira_vpn_get_high_jewel_score(self)`
- `test_blog_low_jewel_score` (line 88) `def test_blog_low_jewel_score(self)`
- `test_partial_data_lower_confidence` (line 95) `def test_partial_data_lower_confidence(self)`
- `test_password_reuse_increases_lateral` (line 102) `def test_password_reuse_increases_lateral(self)`
- `test_serialised_output_no_credentials` (line 109) `def test_serialised_output_no_credentials(self)`

#### `test_tech_fingerprint.py`
**Path:** `tests/test_tech_fingerprint.py`
**File Doc:** *ATDD + BDD tests for estorides_core.tech_fingerprint.  Implements the Given-When-Then contracts declared in ``spec/tech_fingerprint.md``. Property-based invariants live in ``tests/properties/test_tech_fingerprint_properties.py``.*

**Classes:**
- `TestHappyPathFullStack` (line 15) `class TestHappyPathFullStack`
- `TestEmptyResponse` (line 31) `class TestEmptyResponse`
- `TestMalformedVersion` (line 39) `class TestMalformedVersion`
- `TestNoScriptInjection` (line 49) `class TestNoScriptInjection`
- `TestWafDetection` (line 58) `class TestWafDetection`
- `TestCmsDetection` (line 69) `class TestCmsDetection`
- `TestDeduplication` (line 80) `class TestDeduplication`
- `TestInputSizeBound` (line 90) `class TestInputSizeBound`

**Methods:**
- `test_detects_nginx_php_jquery` (line 16) `def test_detects_nginx_php_jquery(self)`
- `test_returns_empty_on_no_input` (line 32) `def test_returns_empty_on_no_input(self)`
- `test_handles_binary_garbage_in_version` (line 40) `def test_handles_binary_garbage_in_version(self)`
- `test_does_not_parse_script_as_tech` (line 50) `def test_does_not_parse_script_as_tech(self)`
- `test_detects_cloudflare_from_headers` (line 59) `def test_detects_cloudflare_from_headers(self)`
- `test_detects_wordpress_from_meta` (line 70) `def test_detects_wordpress_from_meta(self)`
- `test_same_tech_from_multiple_signals_appears_once` (line 81) `def test_same_tech_from_multiple_signals_appears_once(self)`
- `test_only_processes_first_100kb` (line 91) `def test_only_processes_first_100kb(self)`

#### `test_tool_doctor.py`
**Path:** `tests/test_tool_doctor.py`
**File Doc:** *M4 RED tests: tool doctor.*

**Functions:**
- `test_doctor_covers_system_app_binaries` (line 5) `def test_doctor_covers_system_app_binaries()`
- `test_doctor_flags_path_binary` (line 16) `def test_doctor_flags_path_binary()`
- `test_doctor_installable_when_missing_with_recipe` (line 25) `def test_doctor_installable_when_missing_with_recipe()`
- `test_doctor_summary_consistent` (line 35) `def test_doctor_summary_consistent()`

#### `test_tool_install.py`
**Path:** `tests/test_tool_install.py`
**File Doc:** *Tests for estorides_core.tool_install (lazyaddon-style tool installation).  Covers recipe loading, elevation choice (run0 preferred over sudo, none when root), apt vs git install methods, and the TOOL_NOT_FOUND install flow. All subprocess calls are mocked so no network or root access is required.*

**Classes:**
- `TestRecipes` (line 24) `class TestRecipes`
- `TestElevation` (line 44) `class TestElevation`
- `TestInstallFlow` (line 61) `class TestInstallFlow`
- `TestPathTraversal` (line 106) `class TestPathTraversal` - *CodeQL #48/#49/#52: uncontrolled recipe names must never escape
TOOL_RECIPES_DIR; clone targets must stay inside TOOLS_DIR; binary
names are validated before any PATH probe. Only directory-listed
recipe stems ever reach the filesystem.*

**Functions:**
- `no_network` (line 17) `def no_network()` - *Make every _run call a no-op success so tests never touch the network.*

**Methods:**
- `test_recipe_available_for_known_tool` (line 25) `def test_recipe_available_for_known_tool(self)`
- `test_recipe_unavailable_for_unknown_tool` (line 28) `def test_recipe_unavailable_for_unknown_tool(self)`
- `test_load_apt_recipe` (line 31) `def test_load_apt_recipe(self)`
- `test_load_git_recipe` (line 35) `def test_load_git_recipe(self)`
- `test_list_recipes_is_nonempty` (line 39) `def test_list_recipes_is_nonempty(self)`
- `test_run0_preferred_over_sudo` (line 45) `def test_run0_preferred_over_sudo(self)`
- `test_sudo_fallback_when_no_run0` (line 50) `def test_sudo_fallback_when_no_run0(self)`
- `test_no_elevation_when_root` (line 55) `def test_no_elevation_when_root(self)`
- `test_already_installed_noop` (line 62) `def test_already_installed_noop(self, no_network)`
- `test_not_in_allowlist_rejected` (line 67) `def test_not_in_allowlist_rejected(self)`
- `test_no_recipe_rejected` (line 72) `def test_no_recipe_rejected(self)`
- `test_apt_install_success` (line 78) `def test_apt_install_success(self)`
- `test_verify_fails_after_install` (line 89) `def test_verify_fails_after_install(self, no_network)`
- `test_git_install_runs_clone` (line 95) `def test_git_install_runs_clone(self)`
- `test_unknown_valid_name_never_touches_filesystem` (line 112) `def test_unknown_valid_name_never_touches_filesystem(self)`
- `test_install_tool_unknown_recipe_without_fs_probe` (line 117) `def test_install_tool_unknown_recipe_without_fs_probe(self)`
- `test_load_recipe_rejects_traversal` (line 127) `def test_load_recipe_rejects_traversal(self, evil)`
- `test_recipe_path_rejects_traversal` (line 130) `def test_recipe_path_rejects_traversal(self)`
- `test_recipe_available_rejects_traversal` (line 134) `def test_recipe_available_rejects_traversal(self)`
- `test_git_clone_refuses_escape` (line 137) `def test_git_clone_refuses_escape(self)`
- `test_install_tool_rejects_bad_binary_without_path_probe` (line 149) `def test_install_tool_rejects_bad_binary_without_path_probe(self, evil)`
- `test_install_tool_allowlist_checked_before_shortcut` (line 159) `def test_install_tool_allowlist_checked_before_shortcut(self)`
- `test_tool_available_rejects_bad_name` (line 165) `def test_tool_available_rejects_bad_name(self)`

#### `test_tool_runner.py`
**Path:** `tests/test_tool_runner.py`
**File Doc:** *ATDD + BDD tests for estorides_core.tool_runner.  Implements the Given-When-Then contracts declared in ``spec/tool_runner.md``. Property-based invariants live in ``tests/properties/test_tool_runner_properties.py``.*

**Classes:**
- `TestHappyPathNmap` (line 20) `class TestHappyPathNmap`
- `TestInjectionBlocked` (line 29) `class TestInjectionBlocked`
- `TestTimeout` (line 81) `class TestTimeout`
- `TestToolNotFound` (line 96) `class TestToolNotFound`
- `TestToolNotAllowed` (line 117) `class TestToolNotAllowed`
- `TestNoArgs` (line 129) `class TestNoArgs`
- `TestTargetValidation` (line 141) `class TestTargetValidation`
- `TestNonZeroExit` (line 147) `class TestNonZeroExit`
- `TestOutputSha1` (line 156) `class TestOutputSha1`
- `TestConfidenceRange` (line 164) `class TestConfidenceRange`
- `TestOutputTruncation` (line 171) `class TestOutputTruncation`
- `TestErrorResultFields` (line 184) `class TestErrorResultFields`

**Methods:**
- `test_run_tool_returns_result` (line 21) `def test_run_tool_returns_result(self)`
- `test_semicolon_in_arg_rejected` (line 30) `def test_semicolon_in_arg_rejected(self)`
- `test_pipe_in_arg_rejected` (line 40) `def test_pipe_in_arg_rejected(self)`
- `test_backtick_in_arg_rejected` (line 50) `def test_backtick_in_arg_rejected(self)`
- `test_dollar_paren_in_arg_rejected` (line 60) `def test_dollar_paren_in_arg_rejected(self)`
- `test_newline_in_arg_rejected` (line 70) `def test_newline_in_arg_rejected(self)`
- `test_tool_that_exceeds_timeout_returns_tool_timeout` (line 82) `def test_tool_that_exceeds_timeout_returns_tool_timeout(self)`
- `test_tool_not_on_filesystem_returns_error` (line 97) `def test_tool_not_on_filesystem_returns_error(self)`
- `test_disallowed_tool_rejected` (line 118) `def test_disallowed_tool_rejected(self)`
- `test_empty_args_returns_error` (line 130) `def test_empty_args_returns_error(self)`
- `test_control_char_rejected_by_validation` (line 142) `def test_control_char_rejected_by_validation(self)`
- `test_nmap_version_query_succeeds` (line 148) `def test_nmap_version_query_succeeds(self)`
- `test_sha1_is_valid_hex` (line 157) `def test_sha1_is_valid_hex(self)`
- `test_confidence_in_bounds` (line 165) `def test_confidence_in_bounds(self)`
- `test_truncated_flag_when_output_exceeds_limit` (line 172) `def test_truncated_flag_when_output_exceeds_limit(self)`
- `test_error_result_has_fields` (line 185) `def test_error_result_has_fields(self)`
- `test_injection_error_has_fields` (line 206) `def test_injection_error_has_fields(self)`

#### `test_transforms.py`
**Path:** `tests/test_transforms.py`
**File Doc:** *BDD tests for spec/transforms.md — Maltego-style pivoting.  S1 happy, S2 edge vacío, S3 error id, S4 runner-roto, S5 metadata rica, S6 validación input + stream shape, S7 yaml sin código. Rojo hasta que transforms.py gane input_types/output_types/cost + límites.*

**Classes:**
- `Fake` (line 13) `class Fake`

**Functions:**
- `_mock_resolver` (line 10) `def _mock_resolver(monkeypatch, nodes, links, root_id)`
- `test_s1_ip_to_bgp_happy` (line 20) `def test_s1_ip_to_bgp_happy(monkeypatch)`
- `test_s2_empty_osiris_no_raise` (line 35) `def test_s2_empty_osiris_no_raise(monkeypatch)`
- `test_s3_unknown_transform_id` (line 47) `def test_s3_unknown_transform_id()`
- `test_s4_runner_exception_fail_closed` (line 53) `def test_s4_runner_exception_fail_closed()`
- `test_s5_rich_metadata_sorted` (line 67) `def test_s5_rich_metadata_sorted()`
- `test_s6_input_limits_and_stream_shape` (line 80) `def test_s6_input_limits_and_stream_shape()`
- `test_s7_yaml_transform_no_code` (line 96) `def test_s7_yaml_transform_no_code(monkeypatch, tmp_path)`
- `_repo_transforms_dir` (line 130) `def _repo_transforms_dir()`
- `test_s8_yaml_catalog_complete_and_substituted` (line 135) `def test_s8_yaml_catalog_complete_and_substituted()` - *Every transforms/*.yaml ships full metadata; static pivots run
offline with zero '{query}' leftovers, even inside properties.*

**Methods:**
- `resolve` (line 14) `def resolve(self, t, v)`

#### `test_ui_professional.py`
**Path:** `tests/test_ui_professional.py`
**File Doc:** *BDD tests for the ui_professional module (spec/ui_professional.md).  Each scenario maps to a Given-When-From from the spec. We test the JS functions via rendering the template and asserting DOM structure, plus direct Python-side logic for the tier+loading helpers.*

**Classes:**
- `TestS1LoadingAnimation` (line 64) `class TestS1LoadingAnimation` - *S1 -- Loading indicator elements exist in the DOM.*
- `TestS2CriticalExpanded` (line 93) `class TestS2CriticalExpanded` - *S2 -- CRITICAL tier renders expanded with correct badge count.*
- `TestS3NoiseCollapsed` (line 119) `class TestS3NoiseCollapsed`
- `TestS4ToggleExpandCollapse` (line 143) `class TestS4ToggleExpandCollapse`
- `TestS5FallbackFlatView` (line 157) `class TestS5FallbackFlatView`
- `TestS6LoadingTimeout` (line 175) `class TestS6LoadingTimeout`
- `TestS7HoverEffect` (line 184) `class TestS7HoverEffect`
- `TestS8FadeInTransition` (line 197) `class TestS8FadeInTransition`
- `TestS9SecurityCSP` (line 211) `class TestS9SecurityCSP`
- `TestS10XSSSafe` (line 244) `class TestS10XSSSafe`
- `TestIntegrationTierPipeline` (line 262) `class TestIntegrationTierPipeline`

**Functions:**
- `_render_index` (line 27) `def _render_index()`
- `_simulate_tiered_data` (line 39) `def _simulate_tiered_data()`

**Methods:**
- `test_loading_elements_exist` (line 67) `def test_loading_elements_exist(self)`
- `test_loading_css_defined` (line 74) `def test_loading_css_defined(self)`
- `test_js_show_working_indicator_exists` (line 83) `def test_js_show_working_indicator_exists(self)`
- `test_critical_tier_data` (line 96) `def test_critical_tier_data(self)`
- `test_critical_css_classes_exist` (line 105) `def test_critical_css_classes_exist(self)`
- `test_noise_tier_data` (line 120) `def test_noise_tier_data(self)`
- `test_noise_css_classes_exist` (line 127) `def test_noise_css_classes_exist(self)`
- `test_js_toggle_function_exists` (line 134) `def test_js_toggle_function_exists(self)`
- `test_aria_attributes_in_js` (line 144) `def test_aria_attributes_in_js(self)`
- `test_toggle_uses_role_button` (line 149) `def test_toggle_uses_role_button(self)`
- `test_js_fallback_logic` (line 158) `def test_js_fallback_logic(self)`
- `test_tiers_missing_returns_empty` (line 163) `def test_tiers_missing_returns_empty(self)`
- `test_show_toast_exists` (line 176) `def test_show_toast_exists(self)`
- `test_tier_group_hover_css` (line 185) `def test_tier_group_hover_css(self)`
- `test_transition_on_tier_group` (line 189) `def test_transition_on_tier_group(self)`
- `test_fade_in_css_exists` (line 198) `def test_fade_in_css_exists(self)`
- `test_results_use_fade_in` (line 203) `def test_results_use_fade_in(self)`
- `test_no_inline_style_in_tier_badge` (line 212) `def test_no_inline_style_in_tier_badge(self)`
- `test_no_inline_style_in_template` (line 222) `def test_no_inline_style_in_template(self)`
- `test_no_onclick_attributes` (line 233) `def test_no_onclick_attributes(self)`
- `test_escape_html_function_exists` (line 245) `def test_escape_html_function_exists(self)`
- `test_escape_html_properly_defined` (line 249) `def test_escape_html_properly_defined(self)`
- `test_tier_label_uses_text_content` (line 254) `def test_tier_label_uses_text_content(self)`
- `test_tier_summary_accuracy` (line 263) `def test_tier_summary_accuracy(self)`
- `test_every_group_has_required_fields` (line 268) `def test_every_group_has_required_fields(self)`
- `test_scores_are_normalised` (line 280) `def test_scores_are_normalised(self)`

#### `test_ui_visibility.py`
**Path:** `tests/test_ui_visibility.py`
**File Doc:** *Regression tests for the `hidden` attribute contract and output escaping.  `estorides_ui.css` enforces `[hidden] { display: none !important }`, so an element carrying the HTML5 `hidden` attribute can only be revealed by clearing the attribute. Toggling inline `style.display` alone leaves it permanently invisible. These tests pin the JS to the correct pattern.  Also pins that remote-sourced fields interpolated into `innerHTML` are escaped (XSS defence for the fusion entity detail + entity list).*

**Classes:**
- `TestHiddenContract` (line 25) `class TestHiddenContract`
- `TestOutputEscaping` (line 54) `class TestOutputEscaping`

**Functions:**
- `_js` (line 21) `def _js()`

**Methods:**
- `test_css_enforces_hidden` (line 26) `def test_css_enforces_hidden(self)`
- `test_setvisible_clears_hidden_attribute` (line 29) `def test_setvisible_clears_hidden_attribute(self)`
- `test_no_inline_display_reveal_of_hidden_overlays` (line 34) `def test_no_inline_display_reveal_of_hidden_overlays(self)` - *The old buggy patterns that could never reveal a hidden element.*
- `test_tooltip_and_menu_use_setvisible` (line 47) `def test_tooltip_and_menu_use_setvisible(self)`
- `test_entity_list_escapes_type_and_source` (line 55) `def test_entity_list_escapes_type_and_source(self)`
- `test_fusion_detail_escapes_intel_level_and_lists` (line 60) `def test_fusion_detail_escapes_intel_level_and_lists(self)`

#### `test_vuln_correlation.py`
**Path:** `tests/test_vuln_correlation.py`
**File Doc:** *ATDD + BDD tests for estorides_core.vuln_correlation.  Implements the Given-When-Then contracts declared in ``spec/vuln_correlation.md``.*

**Classes:**
- `TestNginxCveMatch` (line 21) `class TestNginxCveMatch`
- `TestMetasploitAvailable` (line 31) `class TestMetasploitAvailable`
- `TestUnknownTechnology` (line 41) `class TestUnknownTechnology`
- `TestDefaultCredentials` (line 50) `class TestDefaultCredentials`
- `TestCriticalPrioritised` (line 62) `class TestCriticalPrioritised`
- `TestNoVersionMatch` (line 71) `class TestNoVersionMatch`
- `TestAttackReadiness` (line 81) `class TestAttackReadiness`
- `TestLocalCveLookup` (line 94) `class TestLocalCveLookup`

**Functions:**
- `_make_tech` (line 16) `def _make_tech(name, version)`

**Methods:**
- `test_returns_cves_for_nginx` (line 22) `def test_returns_cves_for_nginx(self)`
- `test_apache_struts_has_metasploit` (line 32) `def test_apache_struts_has_metasploit(self)`
- `test_unknown_tech_returns_empty` (line 42) `def test_unknown_tech_returns_empty(self)`
- `test_jenkins_has_default_admin` (line 51) `def test_jenkins_has_default_admin(self)`
- `test_most_critical_is_highest_cvss` (line 63) `def test_most_critical_is_highest_cvss(self)`
- `test_no_version_reduces_confidence` (line 72) `def test_no_version_reduces_confidence(self)`
- `test_exploit_available_increases_score` (line 82) `def test_exploit_available_increases_score(self)`
- `test_known_tech_in_local_table` (line 95) `def test_known_tech_in_local_table(self)`

#### `test_web_helpers.py`
**Path:** `tests/test_web_helpers.py`
**File Doc:** *BDD tests for the web-layer decorators/helpers extracted from `create_app`.  - WH1: `_provides` returns 503 when the service is missing - WH2: `_provides` is transparent when the service is present - WH3: `_provides` runs after `require_auth` (auth ordering preserved) - WH4: `_sse_response` sets the shared streaming headers*

**Classes:**
- `TestWH1Missing` (line 24) `class TestWH1Missing`
- `TestWH2Present` (line 39) `class TestWH2Present`
- `TestWH3Ordering` (line 54) `class TestWH3Ordering`
- `TestWH4Sse` (line 72) `class TestWH4Sse`

**Functions:**
- `_app` (line 17) `def _app()`

**Methods:**
- `test_missing_service_edges_503` (line 25) `def test_missing_service_edges_503(self)`
- `test_present_service_passes` (line 40) `def test_present_service_passes(self)`
- `test_guard_runs_after_auth` (line 55) `def test_guard_runs_after_auth(self)`
- `test_sse_headers` (line 73) `def test_sse_headers(self)`
- `view` (line 31) `def view()`
- `view` (line 46) `def view()`
- `view` (line 63) `def view()`
- `stream` (line 77) `def stream()`

#### `test_web_tools_blueprint.py`
**Path:** `tests/test_web_tools_blueprint.py`
**File Doc:** *M5 RED tests: tools blueprint slice.*

**Functions:**
- `test_tools_rules_present` (line 5) `def test_tools_rules_present()`
- `test_blueprint_registered` (line 16) `def test_blueprint_registered()`
- `test_unknown_tool_install_reaches_view` (line 23) `def test_unknown_tool_install_reaches_view()`
- `test_install_route_rejects_bad_binary` (line 34) `def test_install_route_rejects_bad_binary()`

#### `split_sources.py`
**Path:** `tools/split_sources.py`
**File Doc:** *Split legacy grouped source files into one addon per file.  Reads every multi-document ``sources/NN_xxx.yaml`` and writes each source as ``sources/NN_xxx/<name>.yaml``, then removes the original grouped file. The runtime loader recurses, so the tree is picked up automatically (lazyaddons style). Re-running is safe: existing per-addon files are overwritten in place.*

**Functions:**
- `main` (line 19) `def main()`

#### `sync_docs.py`
**Path:** `tools/sync_docs.py`
**File Doc:** *tools.sync_docs =============== Generate docs manifest from source truth and fail on drift.  I scan spec/*.md headings plus estorides_core modules plus the live Flask route table, then write docs/MANIFEST.json. CI runs me with --check so stale docs fail the build instead of rotting silently.*

**Functions:**
- `collect` (line 20) `def collect()` - *Collect specs, core modules and web routes from the tree.*
- `main` (line 36) `def main(argv)` - *Write the manifest or check it for drift.*

#### `web.py`
**Path:** `web.py`
**File Doc:** *Deprecated entry point. Use:  - `python3 estorides_cli.py serve` for the dev server, or - `gunicorn -w 4 wsgi:app` for production.  This file is kept as a redirect so old launchers that import from this module keep working.*

*No symbols extracted*

#### `wsgi.py`
**Path:** `wsgi.py`
**File Doc:** *estorides.wsgi ==============  WSGI entry point for production deployments.  Run with gunicorn (already pinned in requirements.txt):  gunicorn -w 4 -b 0.0.0.0:5050 --timeout 120 --access-logfile - wsgi:app  Environment variables that matter for prod: ESTORIDES_HOST          bind address (default 127.0.0.1 — loopback only) ESTORIDES_PORT          port (default 5050) ESTORIDES_CORS_ORIGINS  comma-separated allowlist for browser CORS ESTORIDES_HSTS          1 to emit Strict-Transport-Security (only behind TLS) ESTORIDES_FORCE_HTTPS   1 to redirect plain http to https (only behind TLS) ESTORIDES_MAX_BODY_BYTES   int, default 1 MiB  The Werkzeug dev server in estorides_web.py is single-threaded and not safe to expose. Do not use `python3 estorides_web.py` in production; use this entry point.*

*No symbols extracted*

### SH (2 files)

#### `_multi_test.sh`
**Path:** `_multi_test.sh`

*No symbols extracted*

#### `install.sh`
**Path:** `install.sh`
**File Doc:** *Bootstrap a venv and install the runtime + optional test dependencies.  Idempotent: re-running on an existing venv is a no-op for the venv step. Tries the full install first (kuzu + aiohttp_socks + gunicorn), then degrades gracefully if a native dep fails to build (e.g. ARM Kali).  Usage: ./install.sh           install everything ./install.sh --minimal install only the minimum required deps ./install.sh --dev     also install test/lint extras*

**Functions:**
- `install_full` (line 51) - *3) Two install passes: full first, minimal fallback. We don't want a single Cython build error to leave the user with a half-installed project — better to fall back to a working baseline and tell them what didn't make it.*
- `install_minimal` (line 55)
