graphrag-codeproperty-graph 0.5.0__tar.gz → 0.5.2__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.
Files changed (46) hide show
  1. graphrag_codeproperty_graph-0.5.2/CHANGELOG.md +82 -0
  2. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/PKG-INFO +53 -13
  3. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/README.md +52 -12
  4. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/pyproject.toml +1 -1
  5. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/src/codeproperty_graph/delta_ingestor.py +1 -0
  6. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/src/codeproperty_graph/vector_loader.py +22 -4
  7. graphrag_codeproperty_graph-0.5.2/tests/test_delta_ingestor_embedding_property.py +129 -0
  8. graphrag_codeproperty_graph-0.5.0/CHANGELOG.md +0 -16
  9. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/.github/workflows/publish.yml +0 -0
  10. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/.gitignore +0 -0
  11. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/CONTRIBUTING.md +0 -0
  12. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/LICENSE +0 -0
  13. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/NOTICE +0 -0
  14. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/docs/ARCHITECTURE.md +0 -0
  15. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/docs/Defining the Master Evidence Graph: A Formal Model for Stateful Software Verdicts.md +0 -0
  16. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/docs/HYBRID-GRAPH.md +0 -0
  17. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/docs/NOTICE.md +0 -0
  18. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/docs/Toward a Master Evidence Graph for Software Verdicts.md +0 -0
  19. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/docs/graphrag_com_redrawn_architecture.md +0 -0
  20. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/docs/graphrag_phased_architecture.md +0 -0
  21. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/docs/neptune_vs_neptune_analytics_cpg_rag_architecture copy.md +0 -0
  22. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/docs/neptune_vs_neptune_analytics_cpg_rag_architecture.md +0 -0
  23. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/examples/README.md +0 -0
  24. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/examples/notebooks/01-CPG-Models-and-GraphDiff.ipynb +0 -0
  25. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/examples/notebooks/02-Delta-Ingestion-Pipeline.ipynb +0 -0
  26. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/examples/notebooks/data/sample_edges.json +0 -0
  27. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/examples/notebooks/data/sample_nodes.json +0 -0
  28. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/sbom/AWS-SERVICES.md +0 -0
  29. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/sbom/SBOM.md +0 -0
  30. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/sbom/codeproperty-graph.cdx.json +0 -0
  31. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/src/codeproperty_graph/__init__.py +0 -0
  32. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/src/codeproperty_graph/artifact_reader.py +0 -0
  33. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/src/codeproperty_graph/artifact_validator.py +0 -0
  34. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/src/codeproperty_graph/code_slice_store.py +0 -0
  35. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/src/codeproperty_graph/cpg_service.py +0 -0
  36. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/src/codeproperty_graph/graph_diff.py +0 -0
  37. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/src/codeproperty_graph/graph_loader.py +0 -0
  38. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/src/codeproperty_graph/graphson_converter.py +0 -0
  39. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/src/codeproperty_graph/manifest_manager.py +0 -0
  40. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/src/codeproperty_graph/models.py +0 -0
  41. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/src/codeproperty_graph/schema.py +0 -0
  42. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/src/codeproperty_graph/summary_overlay.py +0 -0
  43. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/src/codeproperty_graph/tenant_ops.py +0 -0
  44. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/tests/__init__.py +0 -0
  45. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/tests/test_codeproperty_graph.py +0 -0
  46. {graphrag_codeproperty_graph-0.5.0 → graphrag_codeproperty_graph-0.5.2}/tests/test_cpg_service.py +0 -0
@@ -0,0 +1,82 @@
1
+ # Changelog
2
+
3
+ ## [Unreleased]
4
+
5
+ ## [0.5.2] - 2026-09-16
6
+
7
+ ### Fixed
8
+ - **`VectorLoader` no longer writes the raw embedding vector as a Neptune node
9
+ property.** The 0.5.1 on-node write (`SET n += {embedding: <array>, ...}`) was a
10
+ Neo4j-era pattern: Neo4j stores list-valued properties, but the graph backend is
11
+ now Neptune Database, whose openCypher rejects arrays ("Property value must be a
12
+ simple literal"). Every per-node write failed with `BadRequestException`, flooding
13
+ the ingest log with warnings. The embedding **vector** already lives in the vector
14
+ store (AOSS/OpenSearch), which is the sole surface used for kNN retrieval — nothing
15
+ reads the on-node vector. `VectorLoader.load` now writes only **scalar embedding
16
+ provenance** to the node (`hasEmbedding`, `embeddingModel`, `embeddingDimensions`,
17
+ `embeddingTarget`, `embeddedTime`), which Neptune accepts, preserving the
18
+ graph-side "this node has an embedding" signal without duplicating unqueryable data.
19
+ Covered by `tests/test_delta_ingestor_embedding_property.py`
20
+ (`test_vector_loader_writes_scalar_provenance_not_array`). (PSAPI-66782.)
21
+
22
+ ## [0.5.1] - 2026-09-15
23
+
24
+ ### Fixed
25
+ - **`DeltaIngestor.ingest_artifact` now passes `graph_store` to `VectorLoader`.**
26
+ Previously `VectorLoader` was constructed with only `vector_store`, so the
27
+ `graph_store`-guarded step in `VectorLoader.load` — writing each embedding as an
28
+ `embedding` property on the Neptune node (`update_node_properties`) — never
29
+ fired. This restores the client-schema behaviour where CPG nodes carry their
30
+ embedding on the graph node (matching the original cpg-embedding-agent's
31
+ `SET n.embedding`), in addition to the OpenSearch vector index. One-line wiring
32
+ fix; the `SummaryOverlay`/`CodeSliceStore`/`GraphLoader` in the same method
33
+ already received `graph_store`. Covered by
34
+ `tests/test_delta_ingestor_embedding_property.py`. (PSAPI-66782, alignment wave W2.2.)
35
+
36
+ ## [0.5.0] - 2026-08-20
37
+
38
+ ### Added
39
+ - **CPGService** — node-level delta filtering for Neptune ingestion
40
+ - Only writes changed/added METHOD subtrees (not all nodes)
41
+ - AST edge traversal identifies child node ownership
42
+ - Surgical removal of deleted methods via `remove_fn` callback
43
+ - First run (no manifest): full ingest; incremental: filtered by diff
44
+ - 60-90% reduction in Neptune write volume on incremental runs
45
+ - Full artifact pipeline modules (previously in private wheel):
46
+ - `ArtifactReader` — S3 artifact reading and parsing
47
+ - `ArtifactValidator` — schema compliance validation before ingest
48
+ - `GraphLoader` — Neptune batch write with retry cascade
49
+ - `VectorLoader` — OpenSearch Serverless vector ingestion
50
+ - `SummaryOverlay` — LLM summary → Neptune node properties
51
+ - `CodeSliceStore` — source evidence → Neptune node properties
52
+ - `GraphSONConverter` — GraphSON format conversion
53
+ - `Schema` — full Joern CPG schema (20 node types, 14+ edge types)
54
+ - Optimistic locking in ManifestManager (ETag-based)
55
+ - `ManifestConflictError` for concurrent write detection
56
+ - `delete_domain()` and `list_domains()` in tenant_ops
57
+ - Full CPG schema support in models (VectorRecord, SummaryRecord, CodeSliceRecord)
58
+ - 5 new tests for CPGService delta filtering (14 total)
59
+
60
+ ### Changed
61
+ - DeltaIngestor extended with full artifact pipeline (`ingest_artifact`)
62
+ - ManifestManager now supports rollback and optimistic locking
63
+ - Models expanded with full Joern node/edge type documentation
64
+
65
+ ### Dependencies
66
+ - Added `boto3>=1.28.0`
67
+ - `graphrag-document-graph>=3.1.1` (bumped from 3.0.8)
68
+
69
+ ## [0.4.3] - 2026-08-03
70
+
71
+ ### Added
72
+ - Initial public release
73
+ - Joern/Semgrep CPG extraction and ingestion
74
+ - Delta ingestion — skip-or-replace logic to minimize Neptune writes
75
+ - Graph diff comparison between CPG states
76
+ - S3-backed manifest management for repository state tracking
77
+ - AI vulnerability enrichment and risk scoring
78
+ - Tenant lifecycle management (create, purge, replace)
79
+ - CPGNode and CPGEdge Pydantic models (Joern-specific types)
80
+
81
+ ### Dependencies
82
+ - graphrag-document-graph>=3.0.8
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: graphrag-codeproperty-graph
3
- Version: 0.5.0
3
+ Version: 0.5.2
4
4
  Summary: Code Property Graphs — Joern/Semgrep extraction, AI vulnerability enrichment, risk scoring
5
5
  Project-URL: Repository, https://github.com/kanjani-ai-research/codeproperty-graph
6
6
  Author-email: Evan Erwee <evan@erwee.com>
@@ -40,9 +40,8 @@ pip install graphrag-codeproperty-graph
40
40
 
41
41
  ## Dependencies
42
42
 
43
- - `graphrag-document-graph>=3.0.8` — Document graph infrastructure (typed nodes, Cypher builders, multi-tenancy)
44
-
45
- This package does **not** depend on `bona`.
43
+ - `graphrag-document-graph>=3.1.1` — Document graph infrastructure (typed nodes, Cypher builders, multi-tenancy)
44
+ - `boto3>=1.28.0` — AWS SDK for S3 manifest storage and service integrations
46
45
 
47
46
  ## Dependency Chain
48
47
 
@@ -57,12 +56,38 @@ graphrag-codeproperty-graph
57
56
 
58
57
  ## Quick Start
59
58
 
60
- ### Python API Example
59
+ ### CPGService — Delta Node-Level Filtering (Recommended)
60
+
61
+ ```python
62
+ from codeproperty_graph import CPGService
63
+
64
+ # Only writes changed/added method subtrees to Neptune
65
+ service = CPGService(bucket="graphrag-artifacts-705909755305")
66
+
67
+ result = await service.ingest(
68
+ repo="my-service",
69
+ job_id="build-456",
70
+ tenant_id="tenant_abc123",
71
+ nodes_data=joern_nodes,
72
+ edges_data=joern_edges,
73
+ nodes_path="s3://bucket/cpg-exports/my-service/build-456/nodes.json",
74
+ edges_path="s3://bucket/cpg-exports/my-service/build-456/edges.json",
75
+ graph_store=neptune_store,
76
+ write_fn=batch_write_function,
77
+ remove_fn=remove_method_subgraph, # optional: surgical deletion
78
+ )
79
+
80
+ # result: {"status": "SKIPPED"} — no changes
81
+ # result: {"status": "INGESTED", "delta": "+5 -2 ~3 =150",
82
+ # "filtered_nodes": 47, "total_nodes": 5123}
83
+ ```
84
+
85
+ ### DeltaIngestor — Full Artifact Pipeline
61
86
 
62
87
  ```python
63
- from codeproperty_graph import DeltaIngestor, CPGNode, CPGEdge, GraphDiff
88
+ from codeproperty_graph import DeltaIngestor
64
89
 
65
- # Delta ingestion — only writes to Neptune when code actually changed
90
+ # Full pipeline: graph + vectors + summaries + code slices
66
91
  ingestor = DeltaIngestor(bucket="graphrag-artifacts-705909755305")
67
92
 
68
93
  result = await ingestor.ingest(
@@ -116,12 +141,21 @@ previous = await manager.load(repo="my-service")
116
141
 
117
142
  ```
118
143
  src/codeproperty_graph/
119
- ├── __init__.py # Public API: CPGNode, CPGEdge, DeltaIngestor, etc.
120
- ├── models.py # CPGNode, CPGEdge, Manifest — Joern-specific types
144
+ ├── __init__.py # Public API (30 exports)
145
+ ├── models.py # CPGNode, CPGEdge, Manifest, VectorRecord, SummaryRecord, CodeSliceRecord
146
+ ├── schema.py # Full Joern CPG schema (20 node types, 14 edge types)
121
147
  ├── graph_diff.py # Compare two CPG states by method signature
122
- ├── manifest_manager.py # S3-backed state tracking per repository
123
- ├── delta_ingestor.py # Skip-or-replace orchestration with tenant purge
124
- └── tenant_ops.py # Clean lifecycle management (delete_tenant)
148
+ ├── manifest_manager.py # S3-backed state tracking with optimistic locking
149
+ ├── delta_ingestor.py # Full artifact pipeline orchestration
150
+ ├── cpg_service.py # Node-level delta filtering (only write changed methods)
151
+ ├── tenant_ops.py # Lifecycle management (delete_tenant, delete_domain)
152
+ ├── artifact_reader.py # S3 artifact reading and parsing
153
+ ├── artifact_validator.py # Schema compliance validation
154
+ ├── graph_loader.py # Neptune batch write with retry cascade
155
+ ├── vector_loader.py # OpenSearch Serverless vector ingestion
156
+ ├── summary_overlay.py # LLM summaries → Neptune node properties
157
+ ├── code_slice_store.py # Source evidence → Neptune node properties
158
+ └── graphson_converter.py # GraphSON format conversion
125
159
  ```
126
160
 
127
161
  ## Integration
@@ -149,7 +183,13 @@ src/codeproperty_graph/
149
183
  2. Extract METHOD node signatures: `{full_name: hash}`
150
184
  3. Compare against previous manifest in S3
151
185
  4. If identical → **SKIP** (no Neptune writes, saves cost)
152
- 5. If changed → **INGEST** full graph under new tenant, purge old tenant, update manifest
186
+ 5. If changed → **CPGService filters to changed methods only**:
187
+ - Identify added/modified METHOD nodes via `GraphDiff.compare()`
188
+ - Walk AST edges to collect child nodes of changed methods
189
+ - Filter edges to changed subgraph
190
+ - Write only the delta (60-90% fewer Neptune writes)
191
+ - Surgically remove deleted method subgraphs
192
+ - Update manifest with full current state
153
193
 
154
194
  ### With Document Graph
155
195
 
@@ -15,9 +15,8 @@ pip install graphrag-codeproperty-graph
15
15
 
16
16
  ## Dependencies
17
17
 
18
- - `graphrag-document-graph>=3.0.8` — Document graph infrastructure (typed nodes, Cypher builders, multi-tenancy)
19
-
20
- This package does **not** depend on `bona`.
18
+ - `graphrag-document-graph>=3.1.1` — Document graph infrastructure (typed nodes, Cypher builders, multi-tenancy)
19
+ - `boto3>=1.28.0` — AWS SDK for S3 manifest storage and service integrations
21
20
 
22
21
  ## Dependency Chain
23
22
 
@@ -32,12 +31,38 @@ graphrag-codeproperty-graph
32
31
 
33
32
  ## Quick Start
34
33
 
35
- ### Python API Example
34
+ ### CPGService — Delta Node-Level Filtering (Recommended)
35
+
36
+ ```python
37
+ from codeproperty_graph import CPGService
38
+
39
+ # Only writes changed/added method subtrees to Neptune
40
+ service = CPGService(bucket="graphrag-artifacts-705909755305")
41
+
42
+ result = await service.ingest(
43
+ repo="my-service",
44
+ job_id="build-456",
45
+ tenant_id="tenant_abc123",
46
+ nodes_data=joern_nodes,
47
+ edges_data=joern_edges,
48
+ nodes_path="s3://bucket/cpg-exports/my-service/build-456/nodes.json",
49
+ edges_path="s3://bucket/cpg-exports/my-service/build-456/edges.json",
50
+ graph_store=neptune_store,
51
+ write_fn=batch_write_function,
52
+ remove_fn=remove_method_subgraph, # optional: surgical deletion
53
+ )
54
+
55
+ # result: {"status": "SKIPPED"} — no changes
56
+ # result: {"status": "INGESTED", "delta": "+5 -2 ~3 =150",
57
+ # "filtered_nodes": 47, "total_nodes": 5123}
58
+ ```
59
+
60
+ ### DeltaIngestor — Full Artifact Pipeline
36
61
 
37
62
  ```python
38
- from codeproperty_graph import DeltaIngestor, CPGNode, CPGEdge, GraphDiff
63
+ from codeproperty_graph import DeltaIngestor
39
64
 
40
- # Delta ingestion — only writes to Neptune when code actually changed
65
+ # Full pipeline: graph + vectors + summaries + code slices
41
66
  ingestor = DeltaIngestor(bucket="graphrag-artifacts-705909755305")
42
67
 
43
68
  result = await ingestor.ingest(
@@ -91,12 +116,21 @@ previous = await manager.load(repo="my-service")
91
116
 
92
117
  ```
93
118
  src/codeproperty_graph/
94
- ├── __init__.py # Public API: CPGNode, CPGEdge, DeltaIngestor, etc.
95
- ├── models.py # CPGNode, CPGEdge, Manifest — Joern-specific types
119
+ ├── __init__.py # Public API (30 exports)
120
+ ├── models.py # CPGNode, CPGEdge, Manifest, VectorRecord, SummaryRecord, CodeSliceRecord
121
+ ├── schema.py # Full Joern CPG schema (20 node types, 14 edge types)
96
122
  ├── graph_diff.py # Compare two CPG states by method signature
97
- ├── manifest_manager.py # S3-backed state tracking per repository
98
- ├── delta_ingestor.py # Skip-or-replace orchestration with tenant purge
99
- └── tenant_ops.py # Clean lifecycle management (delete_tenant)
123
+ ├── manifest_manager.py # S3-backed state tracking with optimistic locking
124
+ ├── delta_ingestor.py # Full artifact pipeline orchestration
125
+ ├── cpg_service.py # Node-level delta filtering (only write changed methods)
126
+ ├── tenant_ops.py # Lifecycle management (delete_tenant, delete_domain)
127
+ ├── artifact_reader.py # S3 artifact reading and parsing
128
+ ├── artifact_validator.py # Schema compliance validation
129
+ ├── graph_loader.py # Neptune batch write with retry cascade
130
+ ├── vector_loader.py # OpenSearch Serverless vector ingestion
131
+ ├── summary_overlay.py # LLM summaries → Neptune node properties
132
+ ├── code_slice_store.py # Source evidence → Neptune node properties
133
+ └── graphson_converter.py # GraphSON format conversion
100
134
  ```
101
135
 
102
136
  ## Integration
@@ -124,7 +158,13 @@ src/codeproperty_graph/
124
158
  2. Extract METHOD node signatures: `{full_name: hash}`
125
159
  3. Compare against previous manifest in S3
126
160
  4. If identical → **SKIP** (no Neptune writes, saves cost)
127
- 5. If changed → **INGEST** full graph under new tenant, purge old tenant, update manifest
161
+ 5. If changed → **CPGService filters to changed methods only**:
162
+ - Identify added/modified METHOD nodes via `GraphDiff.compare()`
163
+ - Walk AST edges to collect child nodes of changed methods
164
+ - Filter edges to changed subgraph
165
+ - Write only the delta (60-90% fewer Neptune writes)
166
+ - Surgically remove deleted method subgraphs
167
+ - Update manifest with full current state
128
168
 
129
169
  ### With Document Graph
130
170
 
@@ -7,7 +7,7 @@ packages = ["src/codeproperty_graph"]
7
7
 
8
8
  [project]
9
9
  name = "graphrag-codeproperty-graph"
10
- version = "0.5.0"
10
+ version = "0.5.2"
11
11
  description = "Code Property Graphs — Joern/Semgrep extraction, AI vulnerability enrichment, risk scoring"
12
12
  readme = "README.md"
13
13
  requires-python = ">=3.11"
@@ -166,6 +166,7 @@ class DeltaIngestor:
166
166
  vector_loader = VectorLoader(
167
167
  vector_store=vector_store,
168
168
  expected_dimensions=manifest.get("embedding_dimensions", 0),
169
+ graph_store=graph_store,
169
170
  tenant_id=tenant_id,
170
171
  )
171
172
  vector_result = await vector_loader.load(enriched_vectors)
@@ -163,7 +163,17 @@ class VectorLoader:
163
163
  result.total_loaded += count
164
164
  logger.info(f"Loaded {count} vectors for target '{target}'")
165
165
 
166
- # Also write embedding as a node property on Neptune (client schema conformance)
166
+ # Write embedding PROVENANCE (scalar metadata) onto the graph node.
167
+ #
168
+ # The embedding VECTOR itself lives in the vector store (AOSS/OpenSearch),
169
+ # which is the sole surface used for kNN retrieval. We deliberately do NOT
170
+ # write the raw vector as a node property: the original Neo4j schema stored
171
+ # `SET n.embedding = <array>` (Neo4j supports list-valued properties), but
172
+ # the graph backend here is Neptune Database, whose openCypher rejects
173
+ # array/list property values ("Property value must be a simple literal").
174
+ # Storing scalar provenance (model, dimensions, timestamp, presence flag)
175
+ # keeps the "this node has an embedding" graph-side signal — queryable and
176
+ # filterable — without duplicating the vector where nothing reads it.
167
177
  if self._graph_store and records:
168
178
  written = 0
169
179
  for raw in records:
@@ -171,13 +181,21 @@ class VectorLoader:
171
181
  try:
172
182
  await self._graph_store.update_node_properties(
173
183
  record.cpg_node_id,
174
- {"embedding": record.vector, "embeddedTime": record.metadata.get("embedded_time", "")},
184
+ {
185
+ "hasEmbedding": True,
186
+ "embeddingModel": record.embedding_model,
187
+ "embeddingDimensions": (
188
+ record.embedding_dimensions or len(record.vector)
189
+ ),
190
+ "embeddingTarget": record.embedding_target,
191
+ "embeddedTime": record.metadata.get("embedded_time", ""),
192
+ },
175
193
  )
176
194
  written += 1
177
195
  except Exception as e:
178
- logger.debug(f"Failed to write embedding property for {record.cpg_node_id}: {e}")
196
+ logger.debug(f"Failed to write embedding provenance for {record.cpg_node_id}: {e}")
179
197
  if written:
180
- logger.info(f"Wrote embedding property to {written} Neptune nodes")
198
+ logger.info(f"Wrote embedding provenance to {written} Neptune nodes")
181
199
 
182
200
  if result.errors:
183
201
  logger.warning(f"Encountered {len(result.errors)} validation errors during load")
@@ -0,0 +1,129 @@
1
+ """Wave W2.2 — DeltaIngestor must pass graph_store to VectorLoader.
2
+
3
+ When graph_store is passed to VectorLoader, VectorLoader.load also writes the
4
+ embedding as a property on the Neptune node (update_node_properties), which is
5
+ the client-schema behaviour the Azure cpg-embedding-agent had (SET n.embedding).
6
+ Previously ingest_artifact constructed VectorLoader WITHOUT graph_store, so that
7
+ Neptune write silently never fired.
8
+
9
+ This test asserts the wiring: VectorLoader is constructed with the same
10
+ graph_store that GraphLoader / SummaryOverlay receive.
11
+ """
12
+
13
+ import pytest
14
+ from unittest.mock import AsyncMock, MagicMock, patch
15
+
16
+ from codeproperty_graph import DeltaIngestor
17
+
18
+
19
+ def _manifest():
20
+ return {"repo_id": "test-repo", "analysis_run_id": "job-1", "embedding_dimensions": 768}
21
+
22
+
23
+ @pytest.mark.asyncio
24
+ async def test_vector_loader_receives_graph_store():
25
+ ingestor = DeltaIngestor.__new__(DeltaIngestor)
26
+ # Manifest manager: force "changed" so the pipeline proceeds to the loaders.
27
+ mm = MagicMock()
28
+ mm.has_changes.return_value = (True, None)
29
+ ingestor._manifest_mgr = mm
30
+
31
+ graph_store = MagicMock(name="neptune_store")
32
+ vector_store = MagicMock(name="opensearch_store")
33
+
34
+ nodes = [{"id": "m1", "node_type": "METHOD", "full_name": "com.app.A.m", "hash": "h1"}]
35
+ vectors = [{"cpg_node_id": "m1", "embedding_target": "code", "vector": [0.0] * 768, "text": "x"}]
36
+
37
+ dm = "codeproperty_graph.delta_ingestor"
38
+ with patch(f"{dm}.ArtifactValidator") as V, \
39
+ patch(f"{dm}.GraphLoader") as GL, \
40
+ patch(f"{dm}.VectorLoader") as VL, \
41
+ patch(f"{dm}.SummaryOverlay") as SO, \
42
+ patch(f"{dm}.CodeSliceStore") as CS:
43
+
44
+ V.return_value.validate.return_value = MagicMock(valid=True, warnings=[], errors=[])
45
+ GL.return_value.load = AsyncMock(return_value={"nodes_written": 1})
46
+ VL.return_value.load = AsyncMock(return_value=MagicMock(total_loaded=1))
47
+ SO.return_value.apply = AsyncMock(return_value=1)
48
+ CS.return_value.store = AsyncMock(return_value=0)
49
+
50
+ await ingestor.ingest_artifact(
51
+ manifest=_manifest(),
52
+ nodes_data=nodes,
53
+ edges_data=[],
54
+ vectors_data=vectors,
55
+ summaries_data=[],
56
+ code_slices_data=[],
57
+ graph_store=graph_store,
58
+ vector_store=vector_store,
59
+ validate=False,
60
+ )
61
+
62
+ # The crux of W2.2: VectorLoader must be constructed WITH graph_store,
63
+ # so the Neptune embedding-property write can fire.
64
+ assert VL.called, "VectorLoader was not constructed"
65
+ _, kwargs = VL.call_args
66
+ assert kwargs.get("graph_store") is graph_store, (
67
+ "VectorLoader must receive the graph_store (else the Neptune "
68
+ "embedding-property write never fires — the W2.2 regression)"
69
+ )
70
+ # And it should be the SAME store GraphLoader got.
71
+ assert GL.call_args.kwargs.get("graph_store") is graph_store
72
+
73
+
74
+ # ─── Wave (Neptune fix): on-node write must be SCALAR provenance, not the array ──
75
+ #
76
+ # Neptune Database openCypher rejects list/array property values ("Property value
77
+ # must be a simple literal"). The raw embedding vector lives in the vector store
78
+ # (AOSS); the graph node must carry only scalar provenance. This test drives
79
+ # VectorLoader.load directly and asserts what actually reaches update_node_properties.
80
+
81
+ from codeproperty_graph import VectorLoader
82
+
83
+
84
+ @pytest.mark.asyncio
85
+ async def test_vector_loader_writes_scalar_provenance_not_array():
86
+ graph_store = MagicMock(name="neptune_store")
87
+ graph_store.update_node_properties = AsyncMock(return_value=True)
88
+
89
+ vector_store = MagicMock(name="opensearch_store")
90
+ vector_store.put_vectors = AsyncMock(return_value=1)
91
+
92
+ loader = VectorLoader(vector_store=vector_store, graph_store=graph_store)
93
+
94
+ records = [{
95
+ "cpg_node_id": "n1",
96
+ "embedding_target": "class_summary",
97
+ "vector": [0.1] * 768,
98
+ "text": "summary text",
99
+ "embedding_model": "nomic-embed-text",
100
+ "embedding_dimensions": 768,
101
+ "metadata": {"embedded_time": "2026-09-16T07:50:43Z"},
102
+ }]
103
+
104
+ await loader.load(records)
105
+
106
+ # The on-node write must have happened...
107
+ assert graph_store.update_node_properties.await_count == 1
108
+ node_id, props = graph_store.update_node_properties.await_args.args
109
+
110
+ assert node_id == "n1"
111
+ # ...and it must NOT include the raw vector array (the Neptune-rejecting bug).
112
+ assert "embedding" not in props, (
113
+ "VectorLoader must not write the raw embedding array to the Neptune node "
114
+ "(Neptune Database rejects list-valued properties)"
115
+ )
116
+ # ...every value written must be a Neptune-safe scalar literal.
117
+ for key, value in props.items():
118
+ assert not isinstance(value, (list, dict, tuple, set)), (
119
+ f"Node property {key!r} must be a scalar literal, got {type(value).__name__}"
120
+ )
121
+ # ...and the useful provenance is present.
122
+ assert props["hasEmbedding"] is True
123
+ assert props["embeddingModel"] == "nomic-embed-text"
124
+ assert props["embeddingDimensions"] == 768
125
+ assert props["embeddingTarget"] == "class_summary"
126
+ assert props["embeddedTime"] == "2026-09-16T07:50:43Z"
127
+
128
+ # The VECTOR itself still goes to the vector store (AOSS).
129
+ assert vector_store.put_vectors.await_count >= 1
@@ -1,16 +0,0 @@
1
- # Changelog
2
-
3
- ## [0.4.3] - 2026-08-03
4
-
5
- ### Added
6
- - Initial public release
7
- - Joern/Semgrep CPG extraction and ingestion
8
- - Delta ingestion — skip-or-replace logic to minimize Neptune writes
9
- - Graph diff comparison between CPG states
10
- - S3-backed manifest management for repository state tracking
11
- - AI vulnerability enrichment and risk scoring
12
- - Tenant lifecycle management (create, purge, replace)
13
- - CPGNode and CPGEdge Pydantic models (Joern-specific types)
14
-
15
- ### Dependencies
16
- - graphrag-document-graph>=3.0.8