joint-schema 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- joint_schema-0.1.0/PKG-INFO +72 -0
- joint_schema-0.1.0/README.md +49 -0
- joint_schema-0.1.0/joint_schema.egg-info/PKG-INFO +72 -0
- joint_schema-0.1.0/joint_schema.egg-info/SOURCES.txt +16 -0
- joint_schema-0.1.0/joint_schema.egg-info/dependency_links.txt +1 -0
- joint_schema-0.1.0/joint_schema.egg-info/requires.txt +13 -0
- joint_schema-0.1.0/joint_schema.egg-info/top_level.txt +2 -0
- joint_schema-0.1.0/pyproject.toml +48 -0
- joint_schema-0.1.0/setup.cfg +4 -0
- joint_schema-0.1.0/tests/__init__.py +0 -0
- joint_schema-0.1.0/tests/conftest.py +21 -0
- joint_schema-0.1.0/tests/test_core_actors.py +162 -0
- joint_schema-0.1.0/tests/test_core_events.py +231 -0
- joint_schema-0.1.0/tests/test_core_locations.py +187 -0
- joint_schema-0.1.0/tests/test_core_plans.py +283 -0
- joint_schema-0.1.0/tests/test_core_platforms.py +200 -0
- joint_schema-0.1.0/tests/test_core_resources.py +156 -0
- joint_schema-0.1.0/tests/test_core_time.py +172 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: joint-schema
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Multi-domain military operations schema: MIM operational concepts, DISARM influence operation TTPs, and STIX cyber threat intelligence
|
|
5
|
+
License: TBD
|
|
6
|
+
Project-URL: Homepage, https://github.com/tairnyn/joint-schema
|
|
7
|
+
Project-URL: Repository, https://github.com/tairnyn/joint-schema
|
|
8
|
+
Project-URL: Issues, https://github.com/tairnyn/joint-schema/issues
|
|
9
|
+
Requires-Python: >=3.11
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: linkml>=1.8.0
|
|
12
|
+
Requires-Dist: linkml-runtime>=1.8.0
|
|
13
|
+
Requires-Dist: pydantic>=2.0.0
|
|
14
|
+
Requires-Dist: stix2>=3.0.0
|
|
15
|
+
Requires-Dist: PyYAML>=6.0
|
|
16
|
+
Requires-Dist: jsonschema>=4.0.0
|
|
17
|
+
Requires-Dist: click>=8.0.0
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: build>=1.0.0; extra == "dev"
|
|
20
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
21
|
+
Requires-Dist: pytest-cov>=5.0.0; extra == "dev"
|
|
22
|
+
Requires-Dist: ruff>=0.4.0; extra == "dev"
|
|
23
|
+
|
|
24
|
+
# joint-schema
|
|
25
|
+
|
|
26
|
+
Multi-domain military operations schema covering three integrated knowledge domains:
|
|
27
|
+
|
|
28
|
+
| Domain | Description |
|
|
29
|
+
|--------|-------------|
|
|
30
|
+
| **MIM** | Military Information Model — operational concepts, units, tasks, effects |
|
|
31
|
+
| **DISARM** | Influence operation Tactics, Techniques & Procedures (TTPs) |
|
|
32
|
+
| **STIX** | Cyber threat intelligence objects bridged from STIX 2.1 |
|
|
33
|
+
|
|
34
|
+
## Directory structure
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
joint-schema/
|
|
38
|
+
├── schema/
|
|
39
|
+
│ ├── core/ # Shared base classes, common types, enumerations
|
|
40
|
+
│ ├── mim/ # MIM operational concept schemas (LinkML YAML)
|
|
41
|
+
│ ├── disarm/ # DISARM TTP schemas (LinkML YAML)
|
|
42
|
+
│ └── bridge/ # Cross-domain linkage / alignment schemas
|
|
43
|
+
├── generated/ # Auto-generated artifacts (JSON Schema, Pydantic, OWL, …)
|
|
44
|
+
├── llm-context/ # Bundled context files for LLM tooling
|
|
45
|
+
├── docs/ # Documentation source and build output
|
|
46
|
+
└── tests/ # Schema validation and integration tests
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Quickstart
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Python toolchain
|
|
53
|
+
python -m venv .venv
|
|
54
|
+
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
55
|
+
pip install -r requirements.txt
|
|
56
|
+
|
|
57
|
+
# Node toolchain (DOCX generation)
|
|
58
|
+
npm install
|
|
59
|
+
|
|
60
|
+
# Run tests
|
|
61
|
+
pytest
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Generating artifacts
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# JSON Schema from a LinkML source file
|
|
68
|
+
gen-json-schema schema/core/core.yaml > generated/core.schema.json
|
|
69
|
+
|
|
70
|
+
# Pydantic models
|
|
71
|
+
gen-pydantic schema/core/core.yaml > generated/core_models.py
|
|
72
|
+
```
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# joint-schema
|
|
2
|
+
|
|
3
|
+
Multi-domain military operations schema covering three integrated knowledge domains:
|
|
4
|
+
|
|
5
|
+
| Domain | Description |
|
|
6
|
+
|--------|-------------|
|
|
7
|
+
| **MIM** | Military Information Model — operational concepts, units, tasks, effects |
|
|
8
|
+
| **DISARM** | Influence operation Tactics, Techniques & Procedures (TTPs) |
|
|
9
|
+
| **STIX** | Cyber threat intelligence objects bridged from STIX 2.1 |
|
|
10
|
+
|
|
11
|
+
## Directory structure
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
joint-schema/
|
|
15
|
+
├── schema/
|
|
16
|
+
│ ├── core/ # Shared base classes, common types, enumerations
|
|
17
|
+
│ ├── mim/ # MIM operational concept schemas (LinkML YAML)
|
|
18
|
+
│ ├── disarm/ # DISARM TTP schemas (LinkML YAML)
|
|
19
|
+
│ └── bridge/ # Cross-domain linkage / alignment schemas
|
|
20
|
+
├── generated/ # Auto-generated artifacts (JSON Schema, Pydantic, OWL, …)
|
|
21
|
+
├── llm-context/ # Bundled context files for LLM tooling
|
|
22
|
+
├── docs/ # Documentation source and build output
|
|
23
|
+
└── tests/ # Schema validation and integration tests
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quickstart
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# Python toolchain
|
|
30
|
+
python -m venv .venv
|
|
31
|
+
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
32
|
+
pip install -r requirements.txt
|
|
33
|
+
|
|
34
|
+
# Node toolchain (DOCX generation)
|
|
35
|
+
npm install
|
|
36
|
+
|
|
37
|
+
# Run tests
|
|
38
|
+
pytest
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Generating artifacts
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# JSON Schema from a LinkML source file
|
|
45
|
+
gen-json-schema schema/core/core.yaml > generated/core.schema.json
|
|
46
|
+
|
|
47
|
+
# Pydantic models
|
|
48
|
+
gen-pydantic schema/core/core.yaml > generated/core_models.py
|
|
49
|
+
```
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: joint-schema
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Multi-domain military operations schema: MIM operational concepts, DISARM influence operation TTPs, and STIX cyber threat intelligence
|
|
5
|
+
License: TBD
|
|
6
|
+
Project-URL: Homepage, https://github.com/tairnyn/joint-schema
|
|
7
|
+
Project-URL: Repository, https://github.com/tairnyn/joint-schema
|
|
8
|
+
Project-URL: Issues, https://github.com/tairnyn/joint-schema/issues
|
|
9
|
+
Requires-Python: >=3.11
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: linkml>=1.8.0
|
|
12
|
+
Requires-Dist: linkml-runtime>=1.8.0
|
|
13
|
+
Requires-Dist: pydantic>=2.0.0
|
|
14
|
+
Requires-Dist: stix2>=3.0.0
|
|
15
|
+
Requires-Dist: PyYAML>=6.0
|
|
16
|
+
Requires-Dist: jsonschema>=4.0.0
|
|
17
|
+
Requires-Dist: click>=8.0.0
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: build>=1.0.0; extra == "dev"
|
|
20
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
21
|
+
Requires-Dist: pytest-cov>=5.0.0; extra == "dev"
|
|
22
|
+
Requires-Dist: ruff>=0.4.0; extra == "dev"
|
|
23
|
+
|
|
24
|
+
# joint-schema
|
|
25
|
+
|
|
26
|
+
Multi-domain military operations schema covering three integrated knowledge domains:
|
|
27
|
+
|
|
28
|
+
| Domain | Description |
|
|
29
|
+
|--------|-------------|
|
|
30
|
+
| **MIM** | Military Information Model — operational concepts, units, tasks, effects |
|
|
31
|
+
| **DISARM** | Influence operation Tactics, Techniques & Procedures (TTPs) |
|
|
32
|
+
| **STIX** | Cyber threat intelligence objects bridged from STIX 2.1 |
|
|
33
|
+
|
|
34
|
+
## Directory structure
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
joint-schema/
|
|
38
|
+
├── schema/
|
|
39
|
+
│ ├── core/ # Shared base classes, common types, enumerations
|
|
40
|
+
│ ├── mim/ # MIM operational concept schemas (LinkML YAML)
|
|
41
|
+
│ ├── disarm/ # DISARM TTP schemas (LinkML YAML)
|
|
42
|
+
│ └── bridge/ # Cross-domain linkage / alignment schemas
|
|
43
|
+
├── generated/ # Auto-generated artifacts (JSON Schema, Pydantic, OWL, …)
|
|
44
|
+
├── llm-context/ # Bundled context files for LLM tooling
|
|
45
|
+
├── docs/ # Documentation source and build output
|
|
46
|
+
└── tests/ # Schema validation and integration tests
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Quickstart
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Python toolchain
|
|
53
|
+
python -m venv .venv
|
|
54
|
+
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
55
|
+
pip install -r requirements.txt
|
|
56
|
+
|
|
57
|
+
# Node toolchain (DOCX generation)
|
|
58
|
+
npm install
|
|
59
|
+
|
|
60
|
+
# Run tests
|
|
61
|
+
pytest
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Generating artifacts
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# JSON Schema from a LinkML source file
|
|
68
|
+
gen-json-schema schema/core/core.yaml > generated/core.schema.json
|
|
69
|
+
|
|
70
|
+
# Pydantic models
|
|
71
|
+
gen-pydantic schema/core/core.yaml > generated/core_models.py
|
|
72
|
+
```
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
joint_schema.egg-info/PKG-INFO
|
|
4
|
+
joint_schema.egg-info/SOURCES.txt
|
|
5
|
+
joint_schema.egg-info/dependency_links.txt
|
|
6
|
+
joint_schema.egg-info/requires.txt
|
|
7
|
+
joint_schema.egg-info/top_level.txt
|
|
8
|
+
tests/__init__.py
|
|
9
|
+
tests/conftest.py
|
|
10
|
+
tests/test_core_actors.py
|
|
11
|
+
tests/test_core_events.py
|
|
12
|
+
tests/test_core_locations.py
|
|
13
|
+
tests/test_core_plans.py
|
|
14
|
+
tests/test_core_platforms.py
|
|
15
|
+
tests/test_core_resources.py
|
|
16
|
+
tests/test_core_time.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "joint-schema"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Multi-domain military operations schema: MIM operational concepts, DISARM influence operation TTPs, and STIX cyber threat intelligence"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = { text = "TBD" }
|
|
12
|
+
dependencies = [
|
|
13
|
+
"linkml>=1.8.0",
|
|
14
|
+
"linkml-runtime>=1.8.0",
|
|
15
|
+
"pydantic>=2.0.0",
|
|
16
|
+
"stix2>=3.0.0",
|
|
17
|
+
"PyYAML>=6.0",
|
|
18
|
+
"jsonschema>=4.0.0",
|
|
19
|
+
"click>=8.0.0",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[project.urls]
|
|
23
|
+
Homepage = "https://github.com/tairnyn/joint-schema"
|
|
24
|
+
Repository = "https://github.com/tairnyn/joint-schema"
|
|
25
|
+
Issues = "https://github.com/tairnyn/joint-schema/issues"
|
|
26
|
+
|
|
27
|
+
[project.optional-dependencies]
|
|
28
|
+
dev = [
|
|
29
|
+
"build>=1.0.0",
|
|
30
|
+
"pytest>=8.0.0",
|
|
31
|
+
"pytest-cov>=5.0.0",
|
|
32
|
+
"ruff>=0.4.0",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[tool.setuptools.packages.find]
|
|
36
|
+
where = ["."]
|
|
37
|
+
include = ["schema*", "tests*"]
|
|
38
|
+
|
|
39
|
+
[tool.pytest.ini_options]
|
|
40
|
+
testpaths = ["tests"]
|
|
41
|
+
addopts = "-v --tb=short"
|
|
42
|
+
|
|
43
|
+
[tool.ruff]
|
|
44
|
+
line-length = 100
|
|
45
|
+
target-version = "py311"
|
|
46
|
+
|
|
47
|
+
[tool.ruff.lint]
|
|
48
|
+
select = ["E", "F", "I"]
|
|
File without changes
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared pytest configuration for joint-schema tests.
|
|
3
|
+
|
|
4
|
+
Adds the generated Pydantic models directory to sys.path so every test
|
|
5
|
+
file can import generated models without package installation.
|
|
6
|
+
|
|
7
|
+
The directory is resolved (in priority order) from:
|
|
8
|
+
1. GENERATED_PYTHON_DIR env var — set by CI to /tmp/gen-pr
|
|
9
|
+
2. <repo_root>/generated/python/ — default for local development
|
|
10
|
+
"""
|
|
11
|
+
import os
|
|
12
|
+
import sys
|
|
13
|
+
import pathlib
|
|
14
|
+
|
|
15
|
+
REPO_ROOT = pathlib.Path(__file__).parent.parent
|
|
16
|
+
|
|
17
|
+
env_override = os.environ.get("GENERATED_PYTHON_DIR")
|
|
18
|
+
GENERATED_PYTHON = pathlib.Path(env_override) if env_override else REPO_ROOT / "generated" / "python"
|
|
19
|
+
|
|
20
|
+
if str(GENERATED_PYTHON) not in sys.path:
|
|
21
|
+
sys.path.insert(0, str(GENERATED_PYTHON))
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for schema/core/actors.yaml — three-domain actor model.
|
|
3
|
+
|
|
4
|
+
Generated models: generated/python/core__actors_models.py
|
|
5
|
+
"""
|
|
6
|
+
import pytest
|
|
7
|
+
from pydantic import ValidationError
|
|
8
|
+
|
|
9
|
+
from core__actors_models import (
|
|
10
|
+
MilitaryEchelon,
|
|
11
|
+
BranchOfService,
|
|
12
|
+
CombatantSide,
|
|
13
|
+
ThreatActorType,
|
|
14
|
+
ThreatActorSophistication,
|
|
15
|
+
ThreatActorMotivation,
|
|
16
|
+
InfluenceActorType,
|
|
17
|
+
MilitaryUnit,
|
|
18
|
+
ThreatActor,
|
|
19
|
+
InfluenceOperationActor,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# ── Test 1: MilitaryUnit — NATO/MIM compatible unit model ─────────────────────
|
|
24
|
+
|
|
25
|
+
def test_military_unit_minimal_valid():
|
|
26
|
+
"""MilitaryUnit with only required fields (id, name, side) is valid."""
|
|
27
|
+
unit = MilitaryUnit(
|
|
28
|
+
id="jsc:unit/3-bn-69-ar",
|
|
29
|
+
name="3rd Battalion, 69th Armor Regiment",
|
|
30
|
+
side=CombatantSide.FRIENDLY,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
assert unit.id == "jsc:unit/3-bn-69-ar"
|
|
34
|
+
assert unit.name == "3rd Battalion, 69th Armor Regiment"
|
|
35
|
+
assert unit.side == CombatantSide.FRIENDLY
|
|
36
|
+
# Optional fields absent
|
|
37
|
+
assert unit.echelon is None
|
|
38
|
+
assert unit.parent_unit_id is None
|
|
39
|
+
assert unit.branch_of_service is None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_military_unit_full_hierarchy():
|
|
43
|
+
"""MilitaryUnit with echelon, branch, designation, and parent link is valid."""
|
|
44
|
+
battalion = MilitaryUnit(
|
|
45
|
+
id="jsc:unit/3-bn-69-ar",
|
|
46
|
+
name="3-69 AR",
|
|
47
|
+
side=CombatantSide.FRIENDLY,
|
|
48
|
+
echelon=MilitaryEchelon.BATTALION,
|
|
49
|
+
branch_of_service=BranchOfService.ARMY,
|
|
50
|
+
unit_designation="3rd Battalion, 69th Armor Regiment",
|
|
51
|
+
parent_unit_id="jsc:unit/3-abct",
|
|
52
|
+
aliases=["Task Force IRON", "TF 3-69"],
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
assert battalion.echelon == MilitaryEchelon.BATTALION
|
|
56
|
+
assert battalion.branch_of_service == BranchOfService.ARMY
|
|
57
|
+
assert battalion.unit_designation == "3rd Battalion, 69th Armor Regiment"
|
|
58
|
+
assert battalion.parent_unit_id == "jsc:unit/3-abct"
|
|
59
|
+
assert "Task Force IRON" in battalion.aliases
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_military_unit_requires_side():
|
|
63
|
+
"""MilitaryUnit without `side` must raise a ValidationError."""
|
|
64
|
+
with pytest.raises(ValidationError) as exc_info:
|
|
65
|
+
MilitaryUnit(id="jsc:unit/x", name="Unknown Unit")
|
|
66
|
+
fields = {e["loc"][0] for e in exc_info.value.errors()}
|
|
67
|
+
assert "side" in fields
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_military_unit_requires_id_and_name():
|
|
71
|
+
"""MilitaryUnit without id or name must fail validation."""
|
|
72
|
+
with pytest.raises(ValidationError):
|
|
73
|
+
MilitaryUnit(side=CombatantSide.FRIENDLY)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_military_unit_attribution_confidence_bounds():
|
|
77
|
+
"""attribution_confidence must be in [0, 100]; values outside that range fail."""
|
|
78
|
+
# Boundary values are valid
|
|
79
|
+
lo = MilitaryUnit(id="jsc:unit/a", name="A", side=CombatantSide.HOSTILE, attribution_confidence=0)
|
|
80
|
+
hi = MilitaryUnit(id="jsc:unit/b", name="B", side=CombatantSide.HOSTILE, attribution_confidence=100)
|
|
81
|
+
assert lo.attribution_confidence == 0
|
|
82
|
+
assert hi.attribution_confidence == 100
|
|
83
|
+
|
|
84
|
+
with pytest.raises(ValidationError):
|
|
85
|
+
MilitaryUnit(id="jsc:unit/c", name="C", side=CombatantSide.HOSTILE, attribution_confidence=101)
|
|
86
|
+
with pytest.raises(ValidationError):
|
|
87
|
+
MilitaryUnit(id="jsc:unit/d", name="D", side=CombatantSide.HOSTILE, attribution_confidence=-1)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# ── Test 2: ThreatActor — STIX-mapped adversary model ─────────────────────────
|
|
91
|
+
|
|
92
|
+
def test_threat_actor_nation_state():
|
|
93
|
+
"""ThreatActor of type NATION_STATE with sophistication and motivation."""
|
|
94
|
+
actor = ThreatActor(
|
|
95
|
+
id="stix:threat-actor--12345678-1234-1234-1234-123456789abc",
|
|
96
|
+
name="APT-X",
|
|
97
|
+
threat_actor_types=[ThreatActorType.NATION_STATE],
|
|
98
|
+
sophistication=ThreatActorSophistication.ADVANCED,
|
|
99
|
+
primary_motivation=ThreatActorMotivation.IDEOLOGY,
|
|
100
|
+
aliases=["Fancy Bear", "Sofacy", "APT28"],
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
assert ThreatActorType.NATION_STATE in actor.threat_actor_types
|
|
104
|
+
assert actor.sophistication == ThreatActorSophistication.ADVANCED
|
|
105
|
+
assert actor.primary_motivation == ThreatActorMotivation.IDEOLOGY
|
|
106
|
+
assert "Fancy Bear" in actor.aliases
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_threat_actor_minimal():
|
|
110
|
+
"""ThreatActor with only id and name is valid (all other fields optional)."""
|
|
111
|
+
actor = ThreatActor(
|
|
112
|
+
id="jsc:actor/unknown-threat",
|
|
113
|
+
name="Unknown Threat Actor",
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
assert actor.threat_actor_types is None
|
|
117
|
+
assert actor.sophistication is None
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def test_threat_actor_stix_uri_id():
|
|
121
|
+
"""ThreatActor id accepts a full STIX URN format."""
|
|
122
|
+
actor = ThreatActor(
|
|
123
|
+
id="stix:threat-actor--aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
|
|
124
|
+
name="STIX Actor",
|
|
125
|
+
)
|
|
126
|
+
assert actor.id.startswith("stix:")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# ── Test 3: InfluenceOperationActor — DISARM-mapped influence actor ────────────
|
|
130
|
+
|
|
131
|
+
def test_influence_actor_state_media():
|
|
132
|
+
"""InfluenceOperationActor of type STATE_ALIGNED_MEDIA is valid."""
|
|
133
|
+
actor = InfluenceOperationActor(
|
|
134
|
+
id="jsc:actor/rt-network",
|
|
135
|
+
name="RT Network",
|
|
136
|
+
influence_actor_type=InfluenceActorType.STATE_ALIGNED,
|
|
137
|
+
active_platforms=["YouTube", "Telegram", "X/Twitter"],
|
|
138
|
+
attributed_to_state_id="jsc:actor/russian-federation",
|
|
139
|
+
attribution_confidence=78,
|
|
140
|
+
influence_capabilities=["broadcast television", "social media amplification"],
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
assert actor.influence_actor_type == InfluenceActorType.STATE_ALIGNED
|
|
144
|
+
assert "Telegram" in actor.active_platforms
|
|
145
|
+
assert actor.attribution_confidence == 78
|
|
146
|
+
assert "broadcast television" in actor.influence_capabilities
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def test_influence_actor_requires_id_and_name():
|
|
150
|
+
"""InfluenceOperationActor without id and name must fail."""
|
|
151
|
+
with pytest.raises(ValidationError):
|
|
152
|
+
InfluenceOperationActor()
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def test_influence_actor_attribution_confidence_optional():
|
|
156
|
+
"""attribution_confidence is optional; absent means unasserted."""
|
|
157
|
+
actor = InfluenceOperationActor(
|
|
158
|
+
id="jsc:actor/anon-troll-farm",
|
|
159
|
+
name="Anonymous Troll Farm",
|
|
160
|
+
)
|
|
161
|
+
assert actor.attribution_confidence is None
|
|
162
|
+
assert actor.attributed_to_state_id is None
|