graphrag-codeproperty-graph 0.1.0__py3-none-any.whl
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.
- codeproperty_graph/__init__.py +20 -0
- codeproperty_graph/delta_ingestor.py +117 -0
- codeproperty_graph/graph_diff.py +61 -0
- codeproperty_graph/manifest_manager.py +64 -0
- codeproperty_graph/models.py +62 -0
- codeproperty_graph/tenant_ops.py +26 -0
- graphrag_codeproperty_graph-0.1.0.dist-info/METADATA +168 -0
- graphrag_codeproperty_graph-0.1.0.dist-info/RECORD +11 -0
- graphrag_codeproperty_graph-0.1.0.dist-info/WHEEL +4 -0
- graphrag_codeproperty_graph-0.1.0.dist-info/licenses/LICENSE +21 -0
- graphrag_codeproperty_graph-0.1.0.dist-info/licenses/NOTICE +27 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Code Property Graph — domain layer for Joern CPG delta ingestion.
|
|
2
|
+
|
|
3
|
+
Built on document-graph for typed property graph primitives.
|
|
4
|
+
Adds CPG-specific models, delta comparison, manifest management,
|
|
5
|
+
and tenant lifecycle for incremental code analysis.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = "0.1.0"
|
|
9
|
+
|
|
10
|
+
from .models import CPGNode, CPGEdge, Manifest
|
|
11
|
+
from .graph_diff import GraphDiff
|
|
12
|
+
from .manifest_manager import ManifestManager
|
|
13
|
+
from .delta_ingestor import DeltaIngestor
|
|
14
|
+
from .tenant_ops import delete_tenant
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"CPGNode", "CPGEdge", "Manifest",
|
|
18
|
+
"GraphDiff", "ManifestManager", "DeltaIngestor",
|
|
19
|
+
"delete_tenant",
|
|
20
|
+
]
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Delta Ingestor — orchestrates skip-or-replace CPG ingestion."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from .graph_diff import GraphDiff
|
|
8
|
+
from .manifest_manager import ManifestManager
|
|
9
|
+
from .models import Manifest
|
|
10
|
+
from .tenant_ops import delete_tenant
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class DeltaIngestor:
|
|
16
|
+
"""Orchestrates CPG delta ingestion: compare → skip/ingest → purge → update manifest.
|
|
17
|
+
|
|
18
|
+
Usage:
|
|
19
|
+
ingestor = DeltaIngestor(bucket="graphrag-artifacts-705909755305")
|
|
20
|
+
result = await ingestor.ingest(
|
|
21
|
+
repo="amigo-core",
|
|
22
|
+
job_id="uuid",
|
|
23
|
+
nodes_data=[...],
|
|
24
|
+
edges_data=[...],
|
|
25
|
+
graph_store=neptune_store,
|
|
26
|
+
write_fn=my_write_function,
|
|
27
|
+
)
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, bucket: str, prefix: str = "cpg-exports", region: str = "us-east-1"):
|
|
31
|
+
self._manifest_mgr = ManifestManager(bucket, prefix, region)
|
|
32
|
+
|
|
33
|
+
async def ingest(
|
|
34
|
+
self,
|
|
35
|
+
repo: str,
|
|
36
|
+
job_id: str,
|
|
37
|
+
tenant_id: str,
|
|
38
|
+
nodes_data: list[dict],
|
|
39
|
+
edges_data: list[dict],
|
|
40
|
+
nodes_path: str,
|
|
41
|
+
edges_path: str,
|
|
42
|
+
graph_store: Any,
|
|
43
|
+
write_fn=None,
|
|
44
|
+
) -> dict:
|
|
45
|
+
"""Execute delta-aware ingestion.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
repo: Repository name
|
|
49
|
+
job_id: Current job UUID
|
|
50
|
+
tenant_id: Derived tenant for this job
|
|
51
|
+
nodes_data: Parsed node records from Joern export
|
|
52
|
+
edges_data: Parsed edge records from Joern export
|
|
53
|
+
nodes_path: S3 URI to nodes.json
|
|
54
|
+
edges_path: S3 URI to edges.json
|
|
55
|
+
graph_store: Neptune graph store instance
|
|
56
|
+
write_fn: async callable(nodes_data, edges_data, tenant_id, graph_store) → dict
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Dict with status, nodes_written, etc.
|
|
60
|
+
"""
|
|
61
|
+
# Extract method signatures from nodes
|
|
62
|
+
method_sigs = {
|
|
63
|
+
n["full_name"]: n.get("hash", "")
|
|
64
|
+
for n in nodes_data
|
|
65
|
+
if n.get("node_type") == "METHOD" and n.get("full_name")
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
# Check manifest
|
|
69
|
+
changed, previous = self._manifest_mgr.has_changes(repo, method_sigs)
|
|
70
|
+
|
|
71
|
+
if not changed:
|
|
72
|
+
logger.info(f"Delta check: no changes for {repo}, skipping ingest")
|
|
73
|
+
return {
|
|
74
|
+
"status": "SKIPPED",
|
|
75
|
+
"reason": "no_changes",
|
|
76
|
+
"tenant_id": previous.tenant_id,
|
|
77
|
+
"previous_job_id": previous.job_id,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# Log diff if previous exists
|
|
81
|
+
if previous:
|
|
82
|
+
diff = GraphDiff.compare(previous.method_signatures, method_sigs)
|
|
83
|
+
logger.info(f"Delta: {diff.summary} for {repo}")
|
|
84
|
+
|
|
85
|
+
# Perform full ingest
|
|
86
|
+
if write_fn:
|
|
87
|
+
result = await write_fn(nodes_data, edges_data, tenant_id, graph_store)
|
|
88
|
+
else:
|
|
89
|
+
result = {"nodes_written": len(nodes_data), "edges_written": len(edges_data)}
|
|
90
|
+
|
|
91
|
+
# Purge old tenant
|
|
92
|
+
if previous and previous.tenant_id != tenant_id:
|
|
93
|
+
try:
|
|
94
|
+
await delete_tenant(previous.tenant_id, graph_store)
|
|
95
|
+
except Exception:
|
|
96
|
+
pass # logged inside delete_tenant
|
|
97
|
+
|
|
98
|
+
# Update manifest
|
|
99
|
+
new_manifest = Manifest(
|
|
100
|
+
repo=repo,
|
|
101
|
+
signature=self._manifest_mgr.compute_signature(method_sigs),
|
|
102
|
+
job_id=job_id,
|
|
103
|
+
tenant_id=tenant_id,
|
|
104
|
+
exported_at=datetime.now(timezone.utc).isoformat(),
|
|
105
|
+
nodes_path=nodes_path,
|
|
106
|
+
edges_path=edges_path,
|
|
107
|
+
method_signatures=method_sigs,
|
|
108
|
+
)
|
|
109
|
+
self._manifest_mgr.put(new_manifest)
|
|
110
|
+
|
|
111
|
+
result["status"] = "INGESTED"
|
|
112
|
+
result["tenant_id"] = tenant_id
|
|
113
|
+
result["delta"] = GraphDiff.compare(
|
|
114
|
+
previous.method_signatures if previous else {}, method_sigs
|
|
115
|
+
).summary
|
|
116
|
+
|
|
117
|
+
return result
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Graph Diff — compare two CPG states and compute delta."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Dict
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class DiffResult:
|
|
9
|
+
"""Result of comparing two method signature sets."""
|
|
10
|
+
|
|
11
|
+
added: Dict[str, str] = field(default_factory=dict) # full_name → hash (new methods)
|
|
12
|
+
removed: Dict[str, str] = field(default_factory=dict) # full_name → hash (deleted methods)
|
|
13
|
+
modified: Dict[str, str] = field(default_factory=dict) # full_name → new_hash (body changed)
|
|
14
|
+
unchanged: int = 0
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def has_changes(self) -> bool:
|
|
18
|
+
return bool(self.added or self.removed or self.modified)
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def summary(self) -> str:
|
|
22
|
+
return f"+{len(self.added)} -{len(self.removed)} ~{len(self.modified)} ={self.unchanged}"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class GraphDiff:
|
|
26
|
+
"""Compare method signatures between two CPG exports."""
|
|
27
|
+
|
|
28
|
+
@staticmethod
|
|
29
|
+
def compare(
|
|
30
|
+
previous: Dict[str, str],
|
|
31
|
+
current: Dict[str, str],
|
|
32
|
+
) -> DiffResult:
|
|
33
|
+
"""Compare previous vs current method_signatures dicts.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
previous: {full_name: hash} from manifest
|
|
37
|
+
current: {full_name: hash} from new export
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
DiffResult with added/removed/modified/unchanged counts
|
|
41
|
+
"""
|
|
42
|
+
prev_keys = set(previous.keys())
|
|
43
|
+
curr_keys = set(current.keys())
|
|
44
|
+
|
|
45
|
+
added = {k: current[k] for k in curr_keys - prev_keys}
|
|
46
|
+
removed = {k: previous[k] for k in prev_keys - curr_keys}
|
|
47
|
+
|
|
48
|
+
modified = {}
|
|
49
|
+
unchanged = 0
|
|
50
|
+
for k in prev_keys & curr_keys:
|
|
51
|
+
if previous[k] != current[k]:
|
|
52
|
+
modified[k] = current[k]
|
|
53
|
+
else:
|
|
54
|
+
unchanged += 1
|
|
55
|
+
|
|
56
|
+
return DiffResult(
|
|
57
|
+
added=added,
|
|
58
|
+
removed=removed,
|
|
59
|
+
modified=modified,
|
|
60
|
+
unchanged=unchanged,
|
|
61
|
+
)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Manifest Manager — S3-backed CPG state tracking."""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
import boto3
|
|
9
|
+
|
|
10
|
+
from .models import Manifest
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ManifestManager:
|
|
16
|
+
"""Read/write/compare CPG manifests on S3."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, bucket: str, prefix: str = "cpg-exports", region: str = "us-east-1"):
|
|
19
|
+
self._bucket = bucket
|
|
20
|
+
self._prefix = prefix
|
|
21
|
+
self._s3 = boto3.client("s3", region_name=region)
|
|
22
|
+
|
|
23
|
+
def compute_signature(self, method_signatures: dict[str, str]) -> str:
|
|
24
|
+
"""Compute sha256 signature from method full_name:hash pairs."""
|
|
25
|
+
payload = json.dumps(method_signatures, sort_keys=True)
|
|
26
|
+
return "sha256:" + hashlib.sha256(payload.encode()).hexdigest()
|
|
27
|
+
|
|
28
|
+
def get(self, repo: str) -> Optional[Manifest]:
|
|
29
|
+
"""Read manifest for a repo from S3. Returns None if not found."""
|
|
30
|
+
key = f"{self._prefix}/{repo}/manifest.json"
|
|
31
|
+
try:
|
|
32
|
+
resp = self._s3.get_object(Bucket=self._bucket, Key=key)
|
|
33
|
+
data = json.loads(resp["Body"].read())
|
|
34
|
+
return Manifest(**data)
|
|
35
|
+
except Exception:
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
def put(self, manifest: Manifest) -> None:
|
|
39
|
+
"""Write manifest to S3."""
|
|
40
|
+
key = f"{self._prefix}/{manifest.repo}/manifest.json"
|
|
41
|
+
body = json.dumps({
|
|
42
|
+
"repo": manifest.repo,
|
|
43
|
+
"signature": manifest.signature,
|
|
44
|
+
"job_id": manifest.job_id,
|
|
45
|
+
"tenant_id": manifest.tenant_id,
|
|
46
|
+
"exported_at": manifest.exported_at,
|
|
47
|
+
"nodes_path": manifest.nodes_path,
|
|
48
|
+
"edges_path": manifest.edges_path,
|
|
49
|
+
"method_signatures": manifest.method_signatures,
|
|
50
|
+
})
|
|
51
|
+
self._s3.put_object(Bucket=self._bucket, Key=key, Body=body.encode(), ContentType="application/json")
|
|
52
|
+
logger.info(f"Manifest written: s3://{self._bucket}/{key}")
|
|
53
|
+
|
|
54
|
+
def has_changes(self, repo: str, current_signatures: dict[str, str]) -> tuple[bool, Optional[Manifest]]:
|
|
55
|
+
"""Check if current export differs from stored manifest.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
(has_changes: bool, previous_manifest: Optional[Manifest])
|
|
59
|
+
"""
|
|
60
|
+
previous = self.get(repo)
|
|
61
|
+
if not previous:
|
|
62
|
+
return True, None
|
|
63
|
+
new_sig = self.compute_signature(current_signatures)
|
|
64
|
+
return new_sig != previous.signature, previous
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""CPG domain models — typed representations of Joern output."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Dict, Any, Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class CPGNode:
|
|
9
|
+
"""A code property graph node with Joern metadata.
|
|
10
|
+
|
|
11
|
+
Identity: full_name (stable across line shifts)
|
|
12
|
+
Change detection: hash (content fingerprint from Joern)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
id: str
|
|
16
|
+
node_type: str # METHOD, CALL, IDENTIFIER, LITERAL, etc.
|
|
17
|
+
full_name: str = ""
|
|
18
|
+
hash: str = ""
|
|
19
|
+
filename: str = ""
|
|
20
|
+
name: str = ""
|
|
21
|
+
code: str = ""
|
|
22
|
+
signature: str = ""
|
|
23
|
+
line_number: Optional[int] = None
|
|
24
|
+
is_external: bool = False
|
|
25
|
+
properties: Dict[str, Any] = field(default_factory=dict)
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def stable_id(self) -> str:
|
|
29
|
+
"""Content-addressed identity for delta comparison."""
|
|
30
|
+
return self.full_name or self.id
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class CPGEdge:
|
|
35
|
+
"""A code property graph edge with semantic metadata.
|
|
36
|
+
|
|
37
|
+
Joern edge types: AST, CFG, CDG, REACHING_DEF, CALL, ARGUMENT, DOMINATE, etc.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
source_id: str
|
|
41
|
+
target_id: str
|
|
42
|
+
edge_type: str # AST, CFG, CDG, REACHING_DEF, CALL, ARGUMENT
|
|
43
|
+
properties: Dict[str, Any] = field(default_factory=dict)
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def key(self) -> str:
|
|
47
|
+
"""Unique edge identity for diff."""
|
|
48
|
+
return f"{self.source_id}->{self.edge_type}->{self.target_id}"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class Manifest:
|
|
53
|
+
"""CPG extraction manifest — tracks the current graph state for a repo."""
|
|
54
|
+
|
|
55
|
+
repo: str
|
|
56
|
+
signature: str # sha256 of sorted method full_name:hash pairs
|
|
57
|
+
job_id: str
|
|
58
|
+
tenant_id: str
|
|
59
|
+
exported_at: str
|
|
60
|
+
nodes_path: str
|
|
61
|
+
edges_path: str
|
|
62
|
+
method_signatures: Dict[str, str] = field(default_factory=dict) # full_name → hash
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Tenant Operations — lifecycle management for CPG tenants in Neptune."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
async def delete_tenant(tenant_id: str, graph_store) -> int:
|
|
10
|
+
"""Purge all nodes and edges for a tenant from Neptune.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
tenant_id: The tenant scope to delete
|
|
14
|
+
graph_store: Neptune graph store with execute_query method
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
Number of nodes deleted (approximate)
|
|
18
|
+
"""
|
|
19
|
+
cypher = f"MATCH (n) WHERE n.tenant_id = '{tenant_id}' DETACH DELETE n"
|
|
20
|
+
try:
|
|
21
|
+
await asyncio.to_thread(graph_store.execute_query, cypher, {})
|
|
22
|
+
logger.info(f"Tenant purged: {tenant_id}")
|
|
23
|
+
return -1 # Neptune doesn't return count on DELETE
|
|
24
|
+
except Exception as e:
|
|
25
|
+
logger.warning(f"Tenant purge failed for {tenant_id}: {e}")
|
|
26
|
+
raise
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: graphrag-codeproperty-graph
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Code Property Graphs — Joern/Semgrep extraction, AI vulnerability enrichment, risk scoring
|
|
5
|
+
Project-URL: Repository, https://github.com/RW-Lab/codeproperty-graph
|
|
6
|
+
Author-email: Evan Erwee <evan@erwee.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
License-File: NOTICE
|
|
10
|
+
Keywords: code-analysis,cpg,graph,joern,neptune,semgrep,vulnerability
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Topic :: Security
|
|
14
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
15
|
+
Requires-Python: >=3.11
|
|
16
|
+
Requires-Dist: boto3>=1.26.0
|
|
17
|
+
Requires-Dist: document-graph>=0.1.0
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
|
|
20
|
+
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
|
|
21
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
22
|
+
Provides-Extra: graphrag
|
|
23
|
+
Requires-Dist: graphrag-toolkit-lexical-graph>=3.18.0; extra == 'graphrag'
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# Code Property Graph
|
|
27
|
+
|
|
28
|
+
Code Property Graphs — Joern/Semgrep extraction, AI vulnerability enrichment, risk scoring.
|
|
29
|
+
|
|
30
|
+
> **This package depends on AWS GraphRAG Toolkit (graphrag-toolkit-lexical-graph) for graph storage, vector indexing, and retrieval.**
|
|
31
|
+
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
### Install
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install codeproperty-graph
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Python API Example
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from codeproperty_graph import DeltaIngestor, CPGNode, CPGEdge, GraphDiff
|
|
44
|
+
|
|
45
|
+
# Delta ingestion — only writes to Neptune when code actually changed
|
|
46
|
+
ingestor = DeltaIngestor(bucket="graphrag-artifacts-705909755305")
|
|
47
|
+
|
|
48
|
+
result = await ingestor.ingest(
|
|
49
|
+
repo="my-service",
|
|
50
|
+
job_id="build-456",
|
|
51
|
+
tenant_id="tenant_abc123",
|
|
52
|
+
nodes_data=joern_nodes, # Joern CPG export
|
|
53
|
+
edges_data=joern_edges, # Joern CPG export
|
|
54
|
+
nodes_path="s3://bucket/cpg-exports/my-service/build-456/nodes.json",
|
|
55
|
+
edges_path="s3://bucket/cpg-exports/my-service/build-456/edges.json",
|
|
56
|
+
graph_store=neptune_store,
|
|
57
|
+
write_fn=batch_write_function,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# result: {"status": "SKIPPED"} or {"status": "INGESTED", "delta": "+5 -2 ~3 =150"}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Graph Diff — Compare CPG States
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from codeproperty_graph import GraphDiff, CPGNode
|
|
67
|
+
|
|
68
|
+
# Compare current vs previous code analysis
|
|
69
|
+
diff = GraphDiff.compare(
|
|
70
|
+
current_nodes=current_cpg_nodes,
|
|
71
|
+
previous_nodes=previous_cpg_nodes,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
print(f"Added: {len(diff.added)}")
|
|
75
|
+
print(f"Removed: {len(diff.removed)}")
|
|
76
|
+
print(f"Modified: {len(diff.modified)}")
|
|
77
|
+
print(f"Unchanged: {len(diff.unchanged)}")
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Manifest Management
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from codeproperty_graph import ManifestManager
|
|
84
|
+
|
|
85
|
+
# Track CPG state per repository in S3
|
|
86
|
+
manager = ManifestManager(bucket="graphrag-artifacts-705909755305")
|
|
87
|
+
|
|
88
|
+
# Save manifest after successful ingestion
|
|
89
|
+
await manager.save(repo="my-service", job_id="build-456", signatures=method_signatures)
|
|
90
|
+
|
|
91
|
+
# Load previous manifest for diff comparison
|
|
92
|
+
previous = await manager.load(repo="my-service")
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Package Structure
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
src/codeproperty_graph/
|
|
99
|
+
├── __init__.py # Public API: CPGNode, CPGEdge, DeltaIngestor, etc.
|
|
100
|
+
├── models.py # CPGNode, CPGEdge, Manifest — Joern-specific types
|
|
101
|
+
├── graph_diff.py # Compare two CPG states by method signature
|
|
102
|
+
├── manifest_manager.py # S3-backed state tracking per repository
|
|
103
|
+
├── delta_ingestor.py # Skip-or-replace orchestration with tenant purge
|
|
104
|
+
└── tenant_ops.py # Clean lifecycle management (delete_tenant)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Integration
|
|
108
|
+
|
|
109
|
+
### Architecture Stack
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
┌─────────────────────────────────────────────────────┐
|
|
113
|
+
│ codeproperty-graph (this package) │
|
|
114
|
+
│ Joern/Semgrep CPG, delta ingestion, risk scoring │
|
|
115
|
+
├─────────────────────────────────────────────────────┤
|
|
116
|
+
│ document-graph (infra) │
|
|
117
|
+
│ Node, Edge, CypherBuilder, PipelineExecutor │
|
|
118
|
+
│ Multi-tenancy, batch operations │
|
|
119
|
+
├─────────────────────────────────────────────────────┤
|
|
120
|
+
│ graphrag-toolkit-lexical-graph (foundation) │
|
|
121
|
+
│ GraphStore, Neptune writer, AOSS writer │
|
|
122
|
+
│ Lexical indexing, entity resolution, retrieval │
|
|
123
|
+
└─────────────────────────────────────────────────────┘
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Delta Logic
|
|
127
|
+
|
|
128
|
+
1. Joern exports CPG → `nodes.json` + `edges.json`
|
|
129
|
+
2. Extract METHOD node signatures: `{full_name: hash}`
|
|
130
|
+
3. Compare against previous manifest in S3
|
|
131
|
+
4. If identical → **SKIP** (no Neptune writes, saves cost)
|
|
132
|
+
5. If changed → **INGEST** full graph under new tenant, purge old tenant, update manifest
|
|
133
|
+
|
|
134
|
+
### With Document Graph
|
|
135
|
+
|
|
136
|
+
Code Property Graph uses document-graph for typed property graph primitives:
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
# document-graph provides the graph write infrastructure
|
|
140
|
+
from graphrag_toolkit.document_graph.graph_build.cypher_builder import CypherBuilder
|
|
141
|
+
from graphrag_toolkit.document_graph import Node, Edge
|
|
142
|
+
|
|
143
|
+
# codeproperty-graph adds CPG-specific semantics on top
|
|
144
|
+
from codeproperty_graph import CPGNode, CPGEdge
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### AI Vulnerability Enrichment
|
|
148
|
+
|
|
149
|
+
After CPG ingestion, enrich with AI-powered vulnerability analysis:
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
# Query Neptune for high-risk patterns
|
|
153
|
+
# Score methods by complexity, dependency depth, and known CVE proximity
|
|
154
|
+
# Annotate graph with risk scores for downstream consumption
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Requirements
|
|
158
|
+
|
|
159
|
+
- Python >= 3.11
|
|
160
|
+
- `document-graph >= 0.1.0`
|
|
161
|
+
- `boto3 >= 1.26.0`
|
|
162
|
+
- Optional: `graphrag-toolkit-lexical-graph >= 3.18.0`
|
|
163
|
+
|
|
164
|
+
## License
|
|
165
|
+
|
|
166
|
+
MIT — see [LICENSE](LICENSE) for details.
|
|
167
|
+
|
|
168
|
+
See [NOTICE](NOTICE) for third-party acknowledgments.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
codeproperty_graph/__init__.py,sha256=Kbf6nqUzsvTDHcukATDRVIt2zmDaw6JQr527_YhiwU0,612
|
|
2
|
+
codeproperty_graph/delta_ingestor.py,sha256=J-TD7Z7qxKzdhdBOa3sCHTZhJQMmCruXoNRuBK9d6o8,3885
|
|
3
|
+
codeproperty_graph/graph_diff.py,sha256=e6blXSv1JhtsHShuJG_U_c5oRHBGV7yMiC4mBhRKKeM,1870
|
|
4
|
+
codeproperty_graph/manifest_manager.py,sha256=G4R9WqUwN9RzPg3soNEJS-54ryqi82xVI7utxM6s7oU,2394
|
|
5
|
+
codeproperty_graph/models.py,sha256=vsGb5nL8efuyqreqytezmvF4tZK1adn8PbXlb6giQpQ,1713
|
|
6
|
+
codeproperty_graph/tenant_ops.py,sha256=v4zzQuwjbzpePRDlCoMdPMliVF10L4s8pArmAec__C8,837
|
|
7
|
+
graphrag_codeproperty_graph-0.1.0.dist-info/METADATA,sha256=n9NeKdRbxERtJA1l-tNCO1wHUZYCVLQRD99IFJ6NKQw,5941
|
|
8
|
+
graphrag_codeproperty_graph-0.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
9
|
+
graphrag_codeproperty_graph-0.1.0.dist-info/licenses/LICENSE,sha256=uwE9MC3_X48nBQ0WlXxXYbTknQYFB2qBeiKk6CCVxc8,1067
|
|
10
|
+
graphrag_codeproperty_graph-0.1.0.dist-info/licenses/NOTICE,sha256=r_j1bkjjV4lqE3M64cDNd_-JluPv216nqr4qicJdGfI,773
|
|
11
|
+
graphrag_codeproperty_graph-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Evan Erwee
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
NOTICE
|
|
2
|
+
|
|
3
|
+
Code Property Graph
|
|
4
|
+
Copyright (c) 2026 Evan Erwee
|
|
5
|
+
|
|
6
|
+
This project is licensed under the MIT License.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
Third-Party Components
|
|
11
|
+
======================
|
|
12
|
+
|
|
13
|
+
This project builds upon and integrates with the AWS GraphRAG Toolkit
|
|
14
|
+
(lexical-graph), which is licensed under the Apache License, Version 2.0.
|
|
15
|
+
|
|
16
|
+
AWS GraphRAG Toolkit (graphrag-toolkit-lexical-graph)
|
|
17
|
+
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
18
|
+
Licensed under the Apache License, Version 2.0
|
|
19
|
+
https://github.com/awslabs/graphrag-toolkit
|
|
20
|
+
|
|
21
|
+
You may obtain a copy of the Apache License at:
|
|
22
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
Our original additions and modifications in this repository are released
|
|
27
|
+
under the MIT License. See the LICENSE file for the full MIT License text.
|