feat(graph-rag): add lifecycle, curation, export, and exact caches#31
Conversation
📝 WalkthroughWalkthroughThis PR adds authorized graph curation and export APIs, PostgreSQL-backed graph caches and curation storage, deterministic cache and projection identities, cancellable indexing jobs with extraction deadlines, publication invalidation, and database schema support with extensive unit and integration coverage. ChangesGraph contracts and representations
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
actor Client
participant KnowledgeGraphExportService
participant PostgresGraphExportReader
participant PostgresAuthorizedGraphSql
participant GraphExportFormatter
Client->>KnowledgeGraphExportService: request graph export
KnowledgeGraphExportService->>PostgresAuthorizedGraphSql: build authorized scope parameters
KnowledgeGraphExportService->>PostgresGraphExportReader: read authorized graph
PostgresGraphExportReader->>PostgresAuthorizedGraphSql: query visible identities and contributions
PostgresGraphExportReader-->>KnowledgeGraphExportService: GraphExportDocument
KnowledgeGraphExportService->>GraphExportFormatter: format document
GraphExportFormatter-->>Client: export artifact
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 16
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
core/src/main/java/com/orgmemory/core/knowledge/GraphIndexJob.java (1)
106-120: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win
claim()'s new cancellation guard can permanently poison a job once its lease expires.
claim()now throws whencancellationRequestedis true, but neither caller that can reach this state handles it:
GraphIndexingCoordinator.claimNext(...)callsjob.claim(workerId, now, leaseDuration)directly (no try/catch for this specific call), so if aPROCESSINGjob's lease expires whilecancellationRequestedis true, every subsequent poll throws an uncaughtIllegalStateExceptioninstead of transitioning the job toCANCELLED.GraphIndexingCoordinator.fail(...)doesn't checkcancellationRequested()before callingretry(...), so a job that fails for an unrelated reason while cancellation is pending goes back toPENDINGwith the flag still set — hitting the same throw on its next claim attempt.The job only terminates once attempts are exhausted via
failExpiredLease, ending upFAILED/LEASE_EXPIREDinstead ofCANCELLED, after repeatedly throwing on the worker's poll loop.🛠️ Proposed fix: cancel instead of throwing when re-encountered
void claim(String workerId, Instant now, Duration leaseDuration) { - if (cancellationRequested) { - throw new IllegalStateException("a cancelled graph job cannot be claimed"); - } + if (cancellationRequested) { + cancel(now); + throw new GraphIndexingStoppedException( + GraphIndexingStoppedException.Reason.CANCELLED, + "graph indexing was cancelled before it could be re-claimed"); + } status = GraphIndexJobStatus.PROCESSING;Also add an equivalent
cancellationRequested()check ahead ofretry(...)inGraphIndexingCoordinator.fail(...)so a pending cancellation always wins over a normal retry.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@core/src/main/java/com/orgmemory/core/knowledge/GraphIndexJob.java` around lines 106 - 120, Update GraphIndexJob.claim so a cancellation-requested job transitions to CANCELLED instead of throwing, including when reclaiming an expired lease. In GraphIndexingCoordinator.fail, check cancellationRequested() before calling retry(...) and cancel the job when requested; preserve normal retry behavior otherwise.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@apps/api/src/main/java/com/orgmemory/api/knowledge/KnowledgeGraphManagementController.java`:
- Around line 216-231: Remove organizationId from EvidenceRequest and stop
copying request-supplied tenant data in toReference(). Derive the
organization/tenant identifier from the authorized knowledge space or canonical
evidence record when constructing EvidenceReference, while preserving the
remaining request fields and rejecting cross-tenant claims.
In
`@components/graph-rag-core/src/main/java/com/orgmemory/graphrag/cache/RetrievalResultCache.java`:
- Around line 27-38: Make validation mandatory in the cache put contract by
changing the public put(Key, Entry) flow to call requireValidEntry before
storage and delegate to a separate abstract or protected implementation method.
Update existing implementations to override only that delegated storage method,
ensuring custom implementations cannot bypass tenant-scoping validation.
In
`@components/graph-rag-core/src/main/java/com/orgmemory/graphrag/curation/GraphCurationFingerprint.java`:
- Line 45: The fingerprint construction uses ambiguous delimiter-based
concatenation for nested values. In
components/graph-rag-core/src/main/java/com/orgmemory/graphrag/curation/GraphCurationFingerprint.java#L45-L45,
update the keyword encoding to use length-prefixing or structured canonical
serialization. In
components/graph-rag-core/src/main/java/com/orgmemory/graphrag/port/GraphProjectionManifest.java#L29-L59,
frame each serialized entity, relation, and embedding item before combining the
collection; in `#L74-L124`, replace pipe and unit-separator joins with escaped or
length-prefixed field encoding, preserving deterministic output.
In
`@components/graph-rag-core/src/main/java/com/orgmemory/graphrag/curation/GraphCurationRecord.java`:
- Around line 85-122: Update the IdentityAlias and IdentitySuppression record
definitions in GraphCurationRecord to include a required EvidenceReference
governing-evidence field, validate that reference’s organization consistently
with the other curation variants, and propagate it through all construction and
persistence paths. Update GraphCurationStore.append and overlay filtering so
each alias or suppression is persisted and applied only when its governing
evidence is allowed; do not add a separate append evidence parameter.
In
`@components/graph-rag-core/src/main/java/com/orgmemory/graphrag/export/GraphExportFormatter.java`:
- Around line 143-145: Update the csv method to prefix values beginning with
“=”, “+”, “-”, or “@” with a text marker before applying quote escaping and CSV
quoting, so untrusted names and descriptions are treated as text by spreadsheet
applications.
- Around line 84-114: Update the markdown and text methods to include each
EntityRow and RelationRow’s evidence references, matching the deterministic
rendering and formatting already used by the JSON and CSV export branches.
Preserve the existing fields and ordering, and render evidence for both entity
and relation rows so provenance remains auditable.
In
`@components/graph-rag-core/src/main/java/com/orgmemory/graphrag/storage/ProjectionBatchLifecycle.java`:
- Around line 38-45: In the preparation loop of ProjectionBatchLifecycle, add
each retrieved Preparation to prepared before invoking
preparation.prepare(batch). Keep publications.markPrepared(batch, kind, now)
after successful preparation, so abortAndDiscard can clean up partially staged
state if prepare throws.
In `@core/src/main/java/com/orgmemory/core/knowledge/GraphIndexJob.java`:
- Around line 63-70: Add a migration-backed unique constraint or unique index
for graph_index_jobs.idempotency_key, matching the existing database schema
conventions used for graph_projection_heads. Keep the
GraphIndexJob.idempotencyKey mapping aligned with this constraint so duplicate
graph index jobs are rejected at the database level.
In
`@core/src/main/java/com/orgmemory/core/knowledge/KnowledgeGraphCurationService.java`:
- Around line 150-166: Update requireGoverningEvidence to perform an
authorization.check(...) for can_view on the resolved knowledge asset after
validating organization and Knowledge Space ownership. Ensure this per-asset
authorization occurs before the curation is created or cached, while preserving
the existing ownership checks and not accepting forbidden evidence.
In
`@core/src/main/java/com/orgmemory/core/knowledge/KnowledgeGraphExportService.java`:
- Around line 77-100: Update the export authorization flow in the method
containing the space, subject, and entry checks to record
PermissionAuditDecision.DENY before throwing accessDenied() for missing/inactive
spaces, invalid subjects, and denied authorization entries. Also record a
separate audit/result for policy-version resolution failures before propagating
that exception, while preserving the existing successful export audit.
In
`@core/src/main/java/com/orgmemory/core/knowledge/SourceAclSnapshotRepository.java`:
- Around line 16-32: Add migration coverage for both export query shapes: index
the tenant-scoped join/filter columns used by
SourceAclSnapshotRepository.maximumCurrentAclGeneration, relying on existing
foreign-key or unique constraints where they already cover the join keys, and
add a B-tree index for KnowledgeAssetRepository.findActiveIdsInKnowledgeSpace
covering knowledge_assets(organization_id, knowledge_space_id, archived_at)
without removing existing constraints or indexes. Affected sites:
core/src/main/java/com/orgmemory/core/knowledge/SourceAclSnapshotRepository.java:16-32
and
core/src/main/java/com/orgmemory/core/knowledge/KnowledgeAssetRepository.java:18-29;
these repository sites require no direct changes, only the migration indexes.
In
`@core/src/test/java/com/orgmemory/core/knowledge/GraphIndexingCoordinatorTests.java`:
- Around line 212-234: Bound the retry loop in
failedCurrentJobCanResumeWithFreshRetryBudget with a finite iteration cap, such
as a for-loop or explicit counter, while preserving the existing
fail-and-reclaim behavior. Ensure the test fails clearly if job.getStatus()
never reaches GraphIndexJobStatus.FAILED instead of hanging indefinitely.
In
`@core/src/test/java/com/orgmemory/core/knowledge/KnowledgeAssetLifecycleServiceTests.java`:
- Around line 79-90: Update
deleteRetiresCanonicalLedgerThenRemovesDerivedGraphAndCaches to verify the
lifecycle calls with an InOrder verifier, asserting ingestion.retire occurs
before graph.removeRevision and both cache invalidations. Keep the existing
authorization setup and call arguments, but replace the independent interaction
checks with ordered verification.
In `@integrations/authorization-openfga/src/test/openfga/store.fga.yaml`:
- Around line 189-211: Extend the “Graph management is explicit and resource
scoped” test block with ListObjects assertions for can_curate_graph,
can_export_graph, can_delete, and can_rebuild. Add positive enumeration cases
for Laura and empty-result cases for Minh, covering both knowledge_space and
knowledge_asset resources without changing the existing Check assertions.
In
`@integrations/graph-rag-postgres/src/main/java/com/orgmemory/graphrag/postgres/PostgresGraphCurationStore.java`:
- Around line 44-55: The transaction in the idempotent append flow must handle
concurrent first inserts: update the logic around existing(...) and insert(...)
to catch a duplicate-key DataIntegrityViolationException, re-read the record
with existing(...), and return it when the fingerprint matches or raise
CurationConflictException for a conflicting record. Preserve the current
validation and normal insert behavior.
In
`@integrations/graph-rag-postgres/src/main/java/com/orgmemory/graphrag/postgres/PostgresGraphRagCacheStore.java`:
- Around line 39-128: Implement scheduled cleanup for expired rows in both
graph_model_invocation_cache and graph_retrieval_result_cache. Add a maintenance
method near the existing ModelInvocationCache read/write/invalidate logic that
deletes rows where expires_at is at or before the current time, and register it
with the application’s established scheduling mechanism; preserve namespace
invalidation behavior and use the existing JDBC access patterns.
---
Outside diff comments:
In `@core/src/main/java/com/orgmemory/core/knowledge/GraphIndexJob.java`:
- Around line 106-120: Update GraphIndexJob.claim so a cancellation-requested
job transitions to CANCELLED instead of throwing, including when reclaiming an
expired lease. In GraphIndexingCoordinator.fail, check cancellationRequested()
before calling retry(...) and cancel the job when requested; preserve normal
retry behavior otherwise.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 025ee588-27cd-47e1-9e12-a0d938844d55
⛔ Files ignored due to path filters (5)
contracts/openapi.jsonis excluded by!contracts/openapi.jsondocs/decisions/0014-lightrag-lifecycle-curation-and-cache.mdis excluded by!docs/**docs/increments/active/2026-07-23-full-lightrag-semantic-port/design.mdis excluded by!docs/**docs/increments/active/2026-07-23-full-lightrag-semantic-port/plan.mdis excluded by!docs/**docs/research/lightrag-v1.5.4-parity-manifest.mdis excluded by!docs/**
📒 Files selected for processing (67)
apps/api/build.gradle.ktsapps/api/src/main/java/com/orgmemory/api/knowledge/KnowledgeAssetLifecycleController.javaapps/api/src/main/java/com/orgmemory/api/knowledge/KnowledgeGraphManagementController.javaapps/worker/src/main/java/com/orgmemory/worker/graph/GraphIndexingProcessor.javaapps/worker/src/main/java/com/orgmemory/worker/graph/GraphIndexingProperties.javaapps/worker/src/main/java/com/orgmemory/worker/graph/GraphPublicationCommitter.javaapps/worker/src/main/resources/application.ymlapps/worker/src/test/java/com/orgmemory/worker/graph/GraphIndexingProcessorTests.javaapps/worker/src/test/java/com/orgmemory/worker/graph/GraphPublicationCommitterTests.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/cache/CanonicalCacheKeyHasher.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/cache/ModelInvocationCache.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/cache/ModelInvocationCacheKeys.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/cache/RetrievalResultCache.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/cache/RetrievalResultCacheKeys.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/curation/CurationProvenance.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/curation/EffectiveGraphCuration.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/curation/GraphCurationFingerprint.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/curation/GraphCurationOverlay.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/curation/GraphCurationRecord.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/curation/GraphCurationStore.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/curation/GraphIdentityKind.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/curation/GraphIdentityRef.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/export/GraphExportDocument.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/export/GraphExportFormat.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/export/GraphExportFormatter.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/export/GraphExportReader.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/port/GraphProjectionManifest.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/port/GraphRevisionProjection.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/storage/ProjectionBatchLifecycle.javacomponents/graph-rag-core/src/test/java/com/orgmemory/graphrag/cache/CacheKeyContractTests.javacomponents/graph-rag-core/src/test/java/com/orgmemory/graphrag/curation/EffectiveGraphCurationTests.javacomponents/graph-rag-core/src/test/java/com/orgmemory/graphrag/export/GraphExportFormatterTests.javacomponents/graph-rag-core/src/test/java/com/orgmemory/graphrag/port/GraphProjectionManifestTests.javacomponents/graph-rag-testkit/src/main/java/com/orgmemory/graphrag/testkit/InMemoryRetrievalResultCache.javacomponents/graph-rag-testkit/src/test/java/com/orgmemory/graphrag/testkit/ProjectionBatchLifecycleTests.javacomponents/graph-rag-testkit/src/test/java/com/orgmemory/graphrag/testkit/ProjectionContractTests.javacore/build.gradle.ktscore/src/main/java/com/orgmemory/core/knowledge/ClaimedGraphIndex.javacore/src/main/java/com/orgmemory/core/knowledge/GraphIndexJob.javacore/src/main/java/com/orgmemory/core/knowledge/GraphIndexJobRepository.javacore/src/main/java/com/orgmemory/core/knowledge/GraphIndexJobStatus.javacore/src/main/java/com/orgmemory/core/knowledge/GraphIndexJobView.javacore/src/main/java/com/orgmemory/core/knowledge/GraphIndexLifecycleService.javacore/src/main/java/com/orgmemory/core/knowledge/GraphIndexingCoordinator.javacore/src/main/java/com/orgmemory/core/knowledge/GraphIndexingStoppedException.javacore/src/main/java/com/orgmemory/core/knowledge/KnowledgeAssetLifecycleService.javacore/src/main/java/com/orgmemory/core/knowledge/KnowledgeAssetRepository.javacore/src/main/java/com/orgmemory/core/knowledge/KnowledgeGraphCurationCommand.javacore/src/main/java/com/orgmemory/core/knowledge/KnowledgeGraphCurationService.javacore/src/main/java/com/orgmemory/core/knowledge/KnowledgeGraphExportService.javacore/src/main/java/com/orgmemory/core/knowledge/SourceAclSnapshotRepository.javacore/src/main/resources/db/migration/V33__graph_lifecycle_curation_and_cache.sqlcore/src/test/java/com/orgmemory/core/knowledge/GraphIndexingCoordinatorTests.javacore/src/test/java/com/orgmemory/core/knowledge/KnowledgeAssetLifecycleServiceTests.javacore/src/test/java/com/orgmemory/core/knowledge/KnowledgeGraphCurationServiceTests.javacore/src/test/java/com/orgmemory/core/knowledge/KnowledgeGraphExportServiceTests.javaintegrations/authorization-openfga/src/main/openfga/model.fgaintegrations/authorization-openfga/src/test/openfga/store.fga.yamlintegrations/graph-rag-postgres/src/main/java/com/orgmemory/graphrag/postgres/PostgresAuthorizedGraphSql.javaintegrations/graph-rag-postgres/src/main/java/com/orgmemory/graphrag/postgres/PostgresGraphCurationStore.javaintegrations/graph-rag-postgres/src/main/java/com/orgmemory/graphrag/postgres/PostgresGraphExportReader.javaintegrations/graph-rag-postgres/src/main/java/com/orgmemory/graphrag/postgres/PostgresGraphProjectionStore.javaintegrations/graph-rag-postgres/src/main/java/com/orgmemory/graphrag/postgres/PostgresGraphRagAutoConfiguration.javaintegrations/graph-rag-postgres/src/main/java/com/orgmemory/graphrag/postgres/PostgresGraphRagCacheStore.javaintegrations/graph-rag-postgres/src/main/java/com/orgmemory/graphrag/postgres/PostgresModelInvocationCache.javaintegrations/graph-rag-postgres/src/main/java/com/orgmemory/graphrag/postgres/PostgresRetrievalResultCache.javaintegrations/graph-rag-postgres/src/test/java/com/orgmemory/graphrag/postgres/PostgresGraphProjectionStoreIntegrationTests.java
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
- GitHub Check: Backend · Java 25
🧰 Additional context used
📓 Path-based instructions (8)
**/*
📄 CodeRabbit inference engine (CLAUDE.md)
**/*: Before changing unfamiliar Spring Boot 4, Spring Modulith 2, Spring AI 2, Gradle, React, Vite, Tailwind, or TypeScript APIs, consult Context7/current official documentation and the projectorgmemory-*verification skills.
Readdocs/guidelines/agent-safety.mdbefore retrieval, AI, MCP, permission, upload, graph, or export work.
Never commit.envfiles, provider keys, tokens, or customer data.
Run the relevant gates fromdocs/guidelines/testing-harness.md; use a terminating clean test as the context gate, and do not treatbootRunas verification.
Current behavior belongs in architecture/specs only after it exists in code; intent belongs in vision, roadmap, or an active increment, and repository state must not be duplicated across documents.
Files:
components/graph-rag-core/src/main/java/com/orgmemory/graphrag/export/GraphExportReader.javacomponents/graph-rag-core/src/test/java/com/orgmemory/graphrag/port/GraphProjectionManifestTests.javaapps/worker/src/main/resources/application.ymlcomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/curation/GraphIdentityKind.javacore/src/test/java/com/orgmemory/core/knowledge/KnowledgeGraphCurationServiceTests.javacore/src/main/java/com/orgmemory/core/knowledge/GraphIndexJobView.javaapps/worker/src/test/java/com/orgmemory/worker/graph/GraphPublicationCommitterTests.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/export/GraphExportFormat.javaapps/api/build.gradle.ktscomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/curation/CurationProvenance.javaintegrations/authorization-openfga/src/test/openfga/store.fga.yamlcomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/cache/ModelInvocationCache.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/curation/GraphIdentityRef.javacore/src/main/java/com/orgmemory/core/knowledge/GraphIndexJobStatus.javaintegrations/graph-rag-postgres/src/main/java/com/orgmemory/graphrag/postgres/PostgresRetrievalResultCache.javacomponents/graph-rag-testkit/src/main/java/com/orgmemory/graphrag/testkit/InMemoryRetrievalResultCache.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/port/GraphRevisionProjection.javacore/build.gradle.ktscomponents/graph-rag-core/src/test/java/com/orgmemory/graphrag/export/GraphExportFormatterTests.javacore/src/test/java/com/orgmemory/core/knowledge/KnowledgeAssetLifecycleServiceTests.javacore/src/main/java/com/orgmemory/core/knowledge/GraphIndexJobRepository.javacore/src/main/java/com/orgmemory/core/knowledge/SourceAclSnapshotRepository.javaapps/worker/src/main/java/com/orgmemory/worker/graph/GraphIndexingProperties.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/cache/CanonicalCacheKeyHasher.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/curation/GraphCurationStore.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/cache/ModelInvocationCacheKeys.javacomponents/graph-rag-core/src/test/java/com/orgmemory/graphrag/cache/CacheKeyContractTests.javaintegrations/graph-rag-postgres/src/main/java/com/orgmemory/graphrag/postgres/PostgresModelInvocationCache.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/curation/GraphCurationFingerprint.javacore/src/main/java/com/orgmemory/core/knowledge/GraphIndexingStoppedException.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/cache/RetrievalResultCache.javacomponents/graph-rag-testkit/src/test/java/com/orgmemory/graphrag/testkit/ProjectionBatchLifecycleTests.javacomponents/graph-rag-core/src/test/java/com/orgmemory/graphrag/curation/EffectiveGraphCurationTests.javacore/src/test/java/com/orgmemory/core/knowledge/KnowledgeGraphExportServiceTests.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/curation/GraphCurationRecord.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/export/GraphExportDocument.javaapps/api/src/main/java/com/orgmemory/api/knowledge/KnowledgeGraphManagementController.javacore/src/main/java/com/orgmemory/core/knowledge/GraphIndexLifecycleService.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/port/GraphProjectionManifest.javacore/src/main/java/com/orgmemory/core/knowledge/KnowledgeAssetLifecycleService.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/storage/ProjectionBatchLifecycle.javacore/src/main/java/com/orgmemory/core/knowledge/KnowledgeGraphExportService.javacore/src/main/java/com/orgmemory/core/knowledge/KnowledgeGraphCurationCommand.javacore/src/main/java/com/orgmemory/core/knowledge/KnowledgeAssetRepository.javaapps/worker/src/main/java/com/orgmemory/worker/graph/GraphPublicationCommitter.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/curation/EffectiveGraphCuration.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/export/GraphExportFormatter.javacomponents/graph-rag-testkit/src/test/java/com/orgmemory/graphrag/testkit/ProjectionContractTests.javacore/src/test/java/com/orgmemory/core/knowledge/GraphIndexingCoordinatorTests.javaintegrations/graph-rag-postgres/src/main/java/com/orgmemory/graphrag/postgres/PostgresAuthorizedGraphSql.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/curation/GraphCurationOverlay.javaapps/api/src/main/java/com/orgmemory/api/knowledge/KnowledgeAssetLifecycleController.javaintegrations/authorization-openfga/src/main/openfga/model.fgaintegrations/graph-rag-postgres/src/main/java/com/orgmemory/graphrag/postgres/PostgresGraphExportReader.javacore/src/main/resources/db/migration/V33__graph_lifecycle_curation_and_cache.sqlcore/src/main/java/com/orgmemory/core/knowledge/KnowledgeGraphCurationService.javaintegrations/graph-rag-postgres/src/main/java/com/orgmemory/graphrag/postgres/PostgresGraphRagAutoConfiguration.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/cache/RetrievalResultCacheKeys.javacore/src/main/java/com/orgmemory/core/knowledge/GraphIndexingCoordinator.javaintegrations/graph-rag-postgres/src/main/java/com/orgmemory/graphrag/postgres/PostgresGraphProjectionStore.javaapps/worker/src/test/java/com/orgmemory/worker/graph/GraphIndexingProcessorTests.javaintegrations/graph-rag-postgres/src/main/java/com/orgmemory/graphrag/postgres/PostgresGraphRagCacheStore.javaintegrations/graph-rag-postgres/src/main/java/com/orgmemory/graphrag/postgres/PostgresGraphCurationStore.javacore/src/main/java/com/orgmemory/core/knowledge/GraphIndexJob.javaapps/worker/src/main/java/com/orgmemory/worker/graph/GraphIndexingProcessor.javaintegrations/graph-rag-postgres/src/test/java/com/orgmemory/graphrag/postgres/PostgresGraphProjectionStoreIntegrationTests.javacore/src/main/java/com/orgmemory/core/knowledge/ClaimedGraphIndex.java
**/*.{java,sql}
📄 CodeRabbit inference engine (CLAUDE.md)
Pair JPA schema changes with a Flyway migration.
Files:
components/graph-rag-core/src/main/java/com/orgmemory/graphrag/export/GraphExportReader.javacomponents/graph-rag-core/src/test/java/com/orgmemory/graphrag/port/GraphProjectionManifestTests.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/curation/GraphIdentityKind.javacore/src/test/java/com/orgmemory/core/knowledge/KnowledgeGraphCurationServiceTests.javacore/src/main/java/com/orgmemory/core/knowledge/GraphIndexJobView.javaapps/worker/src/test/java/com/orgmemory/worker/graph/GraphPublicationCommitterTests.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/export/GraphExportFormat.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/curation/CurationProvenance.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/cache/ModelInvocationCache.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/curation/GraphIdentityRef.javacore/src/main/java/com/orgmemory/core/knowledge/GraphIndexJobStatus.javaintegrations/graph-rag-postgres/src/main/java/com/orgmemory/graphrag/postgres/PostgresRetrievalResultCache.javacomponents/graph-rag-testkit/src/main/java/com/orgmemory/graphrag/testkit/InMemoryRetrievalResultCache.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/port/GraphRevisionProjection.javacomponents/graph-rag-core/src/test/java/com/orgmemory/graphrag/export/GraphExportFormatterTests.javacore/src/test/java/com/orgmemory/core/knowledge/KnowledgeAssetLifecycleServiceTests.javacore/src/main/java/com/orgmemory/core/knowledge/GraphIndexJobRepository.javacore/src/main/java/com/orgmemory/core/knowledge/SourceAclSnapshotRepository.javaapps/worker/src/main/java/com/orgmemory/worker/graph/GraphIndexingProperties.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/cache/CanonicalCacheKeyHasher.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/curation/GraphCurationStore.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/cache/ModelInvocationCacheKeys.javacomponents/graph-rag-core/src/test/java/com/orgmemory/graphrag/cache/CacheKeyContractTests.javaintegrations/graph-rag-postgres/src/main/java/com/orgmemory/graphrag/postgres/PostgresModelInvocationCache.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/curation/GraphCurationFingerprint.javacore/src/main/java/com/orgmemory/core/knowledge/GraphIndexingStoppedException.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/cache/RetrievalResultCache.javacomponents/graph-rag-testkit/src/test/java/com/orgmemory/graphrag/testkit/ProjectionBatchLifecycleTests.javacomponents/graph-rag-core/src/test/java/com/orgmemory/graphrag/curation/EffectiveGraphCurationTests.javacore/src/test/java/com/orgmemory/core/knowledge/KnowledgeGraphExportServiceTests.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/curation/GraphCurationRecord.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/export/GraphExportDocument.javaapps/api/src/main/java/com/orgmemory/api/knowledge/KnowledgeGraphManagementController.javacore/src/main/java/com/orgmemory/core/knowledge/GraphIndexLifecycleService.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/port/GraphProjectionManifest.javacore/src/main/java/com/orgmemory/core/knowledge/KnowledgeAssetLifecycleService.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/storage/ProjectionBatchLifecycle.javacore/src/main/java/com/orgmemory/core/knowledge/KnowledgeGraphExportService.javacore/src/main/java/com/orgmemory/core/knowledge/KnowledgeGraphCurationCommand.javacore/src/main/java/com/orgmemory/core/knowledge/KnowledgeAssetRepository.javaapps/worker/src/main/java/com/orgmemory/worker/graph/GraphPublicationCommitter.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/curation/EffectiveGraphCuration.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/export/GraphExportFormatter.javacomponents/graph-rag-testkit/src/test/java/com/orgmemory/graphrag/testkit/ProjectionContractTests.javacore/src/test/java/com/orgmemory/core/knowledge/GraphIndexingCoordinatorTests.javaintegrations/graph-rag-postgres/src/main/java/com/orgmemory/graphrag/postgres/PostgresAuthorizedGraphSql.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/curation/GraphCurationOverlay.javaapps/api/src/main/java/com/orgmemory/api/knowledge/KnowledgeAssetLifecycleController.javaintegrations/graph-rag-postgres/src/main/java/com/orgmemory/graphrag/postgres/PostgresGraphExportReader.javacore/src/main/resources/db/migration/V33__graph_lifecycle_curation_and_cache.sqlcore/src/main/java/com/orgmemory/core/knowledge/KnowledgeGraphCurationService.javaintegrations/graph-rag-postgres/src/main/java/com/orgmemory/graphrag/postgres/PostgresGraphRagAutoConfiguration.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/cache/RetrievalResultCacheKeys.javacore/src/main/java/com/orgmemory/core/knowledge/GraphIndexingCoordinator.javaintegrations/graph-rag-postgres/src/main/java/com/orgmemory/graphrag/postgres/PostgresGraphProjectionStore.javaapps/worker/src/test/java/com/orgmemory/worker/graph/GraphIndexingProcessorTests.javaintegrations/graph-rag-postgres/src/main/java/com/orgmemory/graphrag/postgres/PostgresGraphRagCacheStore.javaintegrations/graph-rag-postgres/src/main/java/com/orgmemory/graphrag/postgres/PostgresGraphCurationStore.javacore/src/main/java/com/orgmemory/core/knowledge/GraphIndexJob.javaapps/worker/src/main/java/com/orgmemory/worker/graph/GraphIndexingProcessor.javaintegrations/graph-rag-postgres/src/test/java/com/orgmemory/graphrag/postgres/PostgresGraphProjectionStoreIntegrationTests.javacore/src/main/java/com/orgmemory/core/knowledge/ClaimedGraphIndex.java
**/*.java
📄 CodeRabbit inference engine (CLAUDE.md)
JetBrains IDE inspection is a Java-backend gate only.
Files:
components/graph-rag-core/src/main/java/com/orgmemory/graphrag/export/GraphExportReader.javacomponents/graph-rag-core/src/test/java/com/orgmemory/graphrag/port/GraphProjectionManifestTests.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/curation/GraphIdentityKind.javacore/src/test/java/com/orgmemory/core/knowledge/KnowledgeGraphCurationServiceTests.javacore/src/main/java/com/orgmemory/core/knowledge/GraphIndexJobView.javaapps/worker/src/test/java/com/orgmemory/worker/graph/GraphPublicationCommitterTests.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/export/GraphExportFormat.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/curation/CurationProvenance.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/cache/ModelInvocationCache.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/curation/GraphIdentityRef.javacore/src/main/java/com/orgmemory/core/knowledge/GraphIndexJobStatus.javaintegrations/graph-rag-postgres/src/main/java/com/orgmemory/graphrag/postgres/PostgresRetrievalResultCache.javacomponents/graph-rag-testkit/src/main/java/com/orgmemory/graphrag/testkit/InMemoryRetrievalResultCache.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/port/GraphRevisionProjection.javacomponents/graph-rag-core/src/test/java/com/orgmemory/graphrag/export/GraphExportFormatterTests.javacore/src/test/java/com/orgmemory/core/knowledge/KnowledgeAssetLifecycleServiceTests.javacore/src/main/java/com/orgmemory/core/knowledge/GraphIndexJobRepository.javacore/src/main/java/com/orgmemory/core/knowledge/SourceAclSnapshotRepository.javaapps/worker/src/main/java/com/orgmemory/worker/graph/GraphIndexingProperties.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/cache/CanonicalCacheKeyHasher.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/curation/GraphCurationStore.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/cache/ModelInvocationCacheKeys.javacomponents/graph-rag-core/src/test/java/com/orgmemory/graphrag/cache/CacheKeyContractTests.javaintegrations/graph-rag-postgres/src/main/java/com/orgmemory/graphrag/postgres/PostgresModelInvocationCache.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/curation/GraphCurationFingerprint.javacore/src/main/java/com/orgmemory/core/knowledge/GraphIndexingStoppedException.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/cache/RetrievalResultCache.javacomponents/graph-rag-testkit/src/test/java/com/orgmemory/graphrag/testkit/ProjectionBatchLifecycleTests.javacomponents/graph-rag-core/src/test/java/com/orgmemory/graphrag/curation/EffectiveGraphCurationTests.javacore/src/test/java/com/orgmemory/core/knowledge/KnowledgeGraphExportServiceTests.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/curation/GraphCurationRecord.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/export/GraphExportDocument.javaapps/api/src/main/java/com/orgmemory/api/knowledge/KnowledgeGraphManagementController.javacore/src/main/java/com/orgmemory/core/knowledge/GraphIndexLifecycleService.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/port/GraphProjectionManifest.javacore/src/main/java/com/orgmemory/core/knowledge/KnowledgeAssetLifecycleService.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/storage/ProjectionBatchLifecycle.javacore/src/main/java/com/orgmemory/core/knowledge/KnowledgeGraphExportService.javacore/src/main/java/com/orgmemory/core/knowledge/KnowledgeGraphCurationCommand.javacore/src/main/java/com/orgmemory/core/knowledge/KnowledgeAssetRepository.javaapps/worker/src/main/java/com/orgmemory/worker/graph/GraphPublicationCommitter.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/curation/EffectiveGraphCuration.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/export/GraphExportFormatter.javacomponents/graph-rag-testkit/src/test/java/com/orgmemory/graphrag/testkit/ProjectionContractTests.javacore/src/test/java/com/orgmemory/core/knowledge/GraphIndexingCoordinatorTests.javaintegrations/graph-rag-postgres/src/main/java/com/orgmemory/graphrag/postgres/PostgresAuthorizedGraphSql.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/curation/GraphCurationOverlay.javaapps/api/src/main/java/com/orgmemory/api/knowledge/KnowledgeAssetLifecycleController.javaintegrations/graph-rag-postgres/src/main/java/com/orgmemory/graphrag/postgres/PostgresGraphExportReader.javacore/src/main/java/com/orgmemory/core/knowledge/KnowledgeGraphCurationService.javaintegrations/graph-rag-postgres/src/main/java/com/orgmemory/graphrag/postgres/PostgresGraphRagAutoConfiguration.javacomponents/graph-rag-core/src/main/java/com/orgmemory/graphrag/cache/RetrievalResultCacheKeys.javacore/src/main/java/com/orgmemory/core/knowledge/GraphIndexingCoordinator.javaintegrations/graph-rag-postgres/src/main/java/com/orgmemory/graphrag/postgres/PostgresGraphProjectionStore.javaapps/worker/src/test/java/com/orgmemory/worker/graph/GraphIndexingProcessorTests.javaintegrations/graph-rag-postgres/src/main/java/com/orgmemory/graphrag/postgres/PostgresGraphRagCacheStore.javaintegrations/graph-rag-postgres/src/main/java/com/orgmemory/graphrag/postgres/PostgresGraphCurationStore.javacore/src/main/java/com/orgmemory/core/knowledge/GraphIndexJob.javaapps/worker/src/main/java/com/orgmemory/worker/graph/GraphIndexingProcessor.javaintegrations/graph-rag-postgres/src/test/java/com/orgmemory/graphrag/postgres/PostgresGraphProjectionStoreIntegrationTests.javacore/src/main/java/com/orgmemory/core/knowledge/ClaimedGraphIndex.java
**/*.{yml,yaml,properties}
📄 CodeRabbit inference engine (CLAUDE.md)
Keep
ddl-auto=validatein application configuration.
Files:
apps/worker/src/main/resources/application.ymlintegrations/authorization-openfga/src/test/openfga/store.fga.yaml
core/src/main/java/com/orgmemory/core/{authorization,knowledge,permission}/**/*.java
⚙️ CodeRabbit configuration file
core/src/main/java/com/orgmemory/core/{authorization,knowledge,permission}/**/*.java: Treat PostgreSQL ACL evidence as canonical and OpenFGA as the relationship
authorization decision point. Authorization must fail closed. Filtering
must happen before ranking, LIMIT, graph traversal, answer generation,
export, and citation rendering. Flag metadata or timing leak paths.
Files:
core/src/main/java/com/orgmemory/core/knowledge/GraphIndexJobView.javacore/src/main/java/com/orgmemory/core/knowledge/GraphIndexJobStatus.javacore/src/main/java/com/orgmemory/core/knowledge/GraphIndexJobRepository.javacore/src/main/java/com/orgmemory/core/knowledge/SourceAclSnapshotRepository.javacore/src/main/java/com/orgmemory/core/knowledge/GraphIndexingStoppedException.javacore/src/main/java/com/orgmemory/core/knowledge/GraphIndexLifecycleService.javacore/src/main/java/com/orgmemory/core/knowledge/KnowledgeAssetLifecycleService.javacore/src/main/java/com/orgmemory/core/knowledge/KnowledgeGraphExportService.javacore/src/main/java/com/orgmemory/core/knowledge/KnowledgeGraphCurationCommand.javacore/src/main/java/com/orgmemory/core/knowledge/KnowledgeAssetRepository.javacore/src/main/java/com/orgmemory/core/knowledge/KnowledgeGraphCurationService.javacore/src/main/java/com/orgmemory/core/knowledge/GraphIndexingCoordinator.javacore/src/main/java/com/orgmemory/core/knowledge/GraphIndexJob.javacore/src/main/java/com/orgmemory/core/knowledge/ClaimedGraphIndex.java
integrations/authorization-openfga/**/*
⚙️ CodeRabbit configuration file
integrations/authorization-openfga/**/*: Source-native ACL is a hard ceiling. Flag any parent, organization, role,
or wildcard relation that can broaden source access. Require negative
Check and ListObjects coverage for every new permission path.
Files:
integrations/authorization-openfga/src/test/openfga/store.fga.yamlintegrations/authorization-openfga/src/main/openfga/model.fga
apps/api/src/main/java/**/*.java
⚙️ CodeRabbit configuration file
apps/api/src/main/java/**/*.java: Enforce the browser-BFF and resource-server boundaries. Authentication
must resolve an active internal actor through the explicit issuer and
subject binding. Reject identity, tenant, roles, or permissions supplied
by request payloads, JWT email, or untrusted JWT role claims.
Files:
apps/api/src/main/java/com/orgmemory/api/knowledge/KnowledgeGraphManagementController.javaapps/api/src/main/java/com/orgmemory/api/knowledge/KnowledgeAssetLifecycleController.java
core/src/main/resources/db/migration/*.sql
⚙️ CodeRabbit configuration file
core/src/main/resources/db/migration/*.sql: Flyway migrations are immutable after release. Check tenant isolation,
foreign keys, uniqueness, indexes, append-only evidence semantics, safe
defaults, and compatibility with PostgreSQL 18 plus pgvector.
Files:
core/src/main/resources/db/migration/V33__graph_lifecycle_curation_and_cache.sql
🧠 Learnings (1)
📚 Learning: 2026-07-23T23:30:44.585Z
Learnt from: kl3inIT
Repo: kl3inIT/OrgMemory PR: 30
File: core/src/main/resources/db/migration/V32__evidence_scoped_graph_semantics.sql:0-0
Timestamp: 2026-07-23T23:30:44.585Z
Learning: For OrgMemory PostgreSQL Flyway migrations under core/src/main/resources/db/migration, do not recommend using `CREATE INDEX CONCURRENTLY` or `DROP INDEX CONCURRENTLY` inside application-owned Flyway migration SQL. Flyway’s schema-history connection may hold a transaction that can cause concurrent index operations to wait indefinitely (e.g., on a `virtualxid`), and docs/conventions.md forbids this pattern. If you need large production-table index replacement, pre-stage online index operations via the deployment pipeline (outside Flyway) rather than inside the migration; “ordinary” index replacement is acceptable for unreleased projections before production traffic.
Applied to files:
core/src/main/resources/db/migration/V33__graph_lifecycle_curation_and_cache.sql
🪛 ast-grep (0.44.1)
components/graph-rag-core/src/main/java/com/orgmemory/graphrag/cache/CanonicalCacheKeyHasher.java
[warning] 50-50: Use a randomly-generated IV
Context: byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
Note: [CWE-329] Generation of Predictable IV with CBC Mode.
(random-iv)
🪛 PMD (7.26.0)
apps/worker/src/main/java/com/orgmemory/worker/graph/GraphIndexingProcessor.java
[Medium] 212-213: PreserveStackTrace (Best Practices): Thrown exception does not preserve the stack trace of exception 'timeout' on all code paths
(PreserveStackTrace (Best Practices))
🪛 SQLFluff (4.2.2)
core/src/main/resources/db/migration/V33__graph_lifecycle_curation_and_cache.sql
[error] 101-102: CREATE INDEX should use CONCURRENTLY to avoid locking the table during the build.
(PG01)
[error] 144-145: CREATE INDEX should use CONCURRENTLY to avoid locking the table during the build.
(PG01)
[error] 271-279: CREATE INDEX should use CONCURRENTLY to avoid locking the table during the build.
(PG01)
🪛 Squawk (2.59.0)
core/src/main/resources/db/migration/V33__graph_lifecycle_curation_and_cache.sql
[warning] 2-2: Changing the size of a varchar field requires an ACCESS EXCLUSIVE lock, that will prevent all reads and writes to the table. Use a TEXT field with a CHECK constraint.
(prefer-text-field)
[warning] 3-3: Changing the size of a varchar field requires an ACCESS EXCLUSIVE lock, that will prevent all reads and writes to the table. Use a TEXT field with a CHECK constraint.
(prefer-text-field)
[warning] 13-13: Setting a column NOT NULL blocks reads while the table is scanned. Make the field nullable and use a CHECK constraint instead.
(adding-not-nullable-field)
[warning] 18-26: By default new constraints require a table scan and block writes to the table while that scan occurs. Use NOT VALID with a later VALIDATE CONSTRAINT call.
(constraint-missing-not-valid)
[warning] 27-37: By default new constraints require a table scan and block writes to the table while that scan occurs. Use NOT VALID with a later VALIDATE CONSTRAINT call.
(constraint-missing-not-valid)
[warning] 38-42: By default new constraints require a table scan and block writes to the table while that scan occurs. Use NOT VALID with a later VALIDATE CONSTRAINT call.
(constraint-missing-not-valid)
[warning] 43-53: By default new constraints require a table scan and block writes to the table while that scan occurs. Use NOT VALID with a later VALIDATE CONSTRAINT call.
(constraint-missing-not-valid)
[warning] 56-56: Changing the size of a varchar field requires an ACCESS EXCLUSIVE lock, that will prevent all reads and writes to the table. Use a TEXT field with a CHECK constraint.
(prefer-text-field)
[warning] 57-57: Changing the size of a varchar field requires an ACCESS EXCLUSIVE lock, that will prevent all reads and writes to the table. Use a TEXT field with a CHECK constraint.
(prefer-text-field)
[warning] 58-68: By default new constraints require a table scan and block writes to the table while that scan occurs. Use NOT VALID with a later VALIDATE CONSTRAINT call.
(constraint-missing-not-valid)
[warning] 70-72: During normal index creation, table updates are blocked, but reads are still allowed. Use concurrently to avoid blocking writes.
(require-concurrent-index-creation)
[warning] 76-76: Changing the size of a varchar field requires an ACCESS EXCLUSIVE lock, that will prevent all reads and writes to the table. Use a TEXT field with a CHECK constraint.
(prefer-text-field)
[warning] 77-77: Changing the size of a varchar field requires an ACCESS EXCLUSIVE lock, that will prevent all reads and writes to the table. Use a TEXT field with a CHECK constraint.
(prefer-text-field)
[warning] 78-78: Changing the size of a varchar field requires an ACCESS EXCLUSIVE lock, that will prevent all reads and writes to the table. Use a TEXT field with a CHECK constraint.
(prefer-text-field)
[warning] 79-79: Changing the size of a varchar field requires an ACCESS EXCLUSIVE lock, that will prevent all reads and writes to the table. Use a TEXT field with a CHECK constraint.
(prefer-text-field)
[warning] 80-80: Changing the size of a varchar field requires an ACCESS EXCLUSIVE lock, that will prevent all reads and writes to the table. Use a TEXT field with a CHECK constraint.
(prefer-text-field)
[warning] 81-81: Changing the size of a varchar field requires an ACCESS EXCLUSIVE lock, that will prevent all reads and writes to the table. Use a TEXT field with a CHECK constraint.
(prefer-text-field)
[warning] 82-82: Changing the size of a varchar field requires an ACCESS EXCLUSIVE lock, that will prevent all reads and writes to the table. Use a TEXT field with a CHECK constraint.
(prefer-text-field)
[warning] 107-107: Changing the size of a varchar field requires an ACCESS EXCLUSIVE lock, that will prevent all reads and writes to the table. Use a TEXT field with a CHECK constraint.
(prefer-text-field)
[warning] 108-108: Changing the size of a varchar field requires an ACCESS EXCLUSIVE lock, that will prevent all reads and writes to the table. Use a TEXT field with a CHECK constraint.
(prefer-text-field)
[warning] 111-111: Changing the size of a varchar field requires an ACCESS EXCLUSIVE lock, that will prevent all reads and writes to the table. Use a TEXT field with a CHECK constraint.
(prefer-text-field)
[warning] 112-112: Changing the size of a varchar field requires an ACCESS EXCLUSIVE lock, that will prevent all reads and writes to the table. Use a TEXT field with a CHECK constraint.
(prefer-text-field)
[warning] 113-113: Changing the size of a varchar field requires an ACCESS EXCLUSIVE lock, that will prevent all reads and writes to the table. Use a TEXT field with a CHECK constraint.
(prefer-text-field)
[warning] 114-114: Changing the size of a varchar field requires an ACCESS EXCLUSIVE lock, that will prevent all reads and writes to the table. Use a TEXT field with a CHECK constraint.
(prefer-text-field)
[warning] 115-115: Changing the size of a varchar field requires an ACCESS EXCLUSIVE lock, that will prevent all reads and writes to the table. Use a TEXT field with a CHECK constraint.
(prefer-text-field)
[warning] 116-116: Changing the size of a varchar field requires an ACCESS EXCLUSIVE lock, that will prevent all reads and writes to the table. Use a TEXT field with a CHECK constraint.
(prefer-text-field)
[warning] 117-117: Changing the size of a varchar field requires an ACCESS EXCLUSIVE lock, that will prevent all reads and writes to the table. Use a TEXT field with a CHECK constraint.
(prefer-text-field)
[warning] 150-150: Using 32-bit integer fields can result in hitting the max int limit. Use 64-bit integer values instead to prevent hitting this limit.
(prefer-bigint-over-int)
[warning] 183-183: Changing the size of a varchar field requires an ACCESS EXCLUSIVE lock, that will prevent all reads and writes to the table. Use a TEXT field with a CHECK constraint.
(prefer-text-field)
[warning] 184-184: Changing the size of a varchar field requires an ACCESS EXCLUSIVE lock, that will prevent all reads and writes to the table. Use a TEXT field with a CHECK constraint.
(prefer-text-field)
[warning] 185-185: Changing the size of a varchar field requires an ACCESS EXCLUSIVE lock, that will prevent all reads and writes to the table. Use a TEXT field with a CHECK constraint.
(prefer-text-field)
[warning] 186-186: Changing the size of a varchar field requires an ACCESS EXCLUSIVE lock, that will prevent all reads and writes to the table. Use a TEXT field with a CHECK constraint.
(prefer-text-field)
[warning] 192-192: Changing the size of a varchar field requires an ACCESS EXCLUSIVE lock, that will prevent all reads and writes to the table. Use a TEXT field with a CHECK constraint.
(prefer-text-field)
[warning] 202-202: Changing the size of a varchar field requires an ACCESS EXCLUSIVE lock, that will prevent all reads and writes to the table. Use a TEXT field with a CHECK constraint.
(prefer-text-field)
[warning] 206-206: Changing the size of a varchar field requires an ACCESS EXCLUSIVE lock, that will prevent all reads and writes to the table. Use a TEXT field with a CHECK constraint.
(prefer-text-field)
[warning] 207-207: Changing the size of a varchar field requires an ACCESS EXCLUSIVE lock, that will prevent all reads and writes to the table. Use a TEXT field with a CHECK constraint.
(prefer-text-field)
| record EvidenceRequest( | ||
| UUID organizationId, | ||
| UUID knowledgeAssetId, | ||
| UUID sourceRevisionId, | ||
| UUID chunkId, | ||
| UUID aclSnapshotId, | ||
| long aclGeneration) { | ||
|
|
||
| EvidenceReference toReference() { | ||
| return new EvidenceReference( | ||
| organizationId, | ||
| knowledgeAssetId, | ||
| sourceRevisionId, | ||
| chunkId, | ||
| aclSnapshotId, | ||
| aclGeneration); |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Do not accept the evidence tenant from the request body.
organizationId is client-controlled and is copied directly into EvidenceReference. Derive the tenant from the authorized knowledge space or canonical evidence record instead, and remove it from this DTO; otherwise callers can submit cross-tenant evidence claims.
As per path instructions, “Reject identity, tenant, roles, or permissions supplied by request payloads.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@apps/api/src/main/java/com/orgmemory/api/knowledge/KnowledgeGraphManagementController.java`
around lines 216 - 231, Remove organizationId from EvidenceRequest and stop
copying request-supplied tenant data in toReference(). Derive the
organization/tenant identifier from the authorized knowledge space or canonical
evidence record when constructing EvidenceReference, while preserving the
remaining request fields and rejecting cross-tenant claims.
Source: Path instructions
| static void requireValidEntry(Key key, Entry entry) { | ||
| Objects.requireNonNull(key, "key"); | ||
| Objects.requireNonNull(entry, "entry"); | ||
| var organizationId = key.snapshot().namespace().organizationId(); | ||
| if (entry.evidence().stream() | ||
| .anyMatch(evidence -> | ||
| !organizationId.equals(evidence.organizationId()))) { | ||
| throw new IllegalArgumentException( | ||
| "retrieval cache evidence must belong to the key organization"); | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
rg -n -C4 '\brequireValidEntry\s*\(' --type=java
rg -n -C6 'implements\s+RetrievalResultCache\b' --type=javaRepository: kl3inIT/OrgMemory
Length of output: 155
🏁 Script executed:
#!/bin/bash
set -u
echo "Files named RetrievalResultCache.java:"
fd -a 'RetrievalResultCache\.java$' . || true
echo
echo "All calls/usages of requireValidEntry:"
rg -n 'requireValidEntry\b' . || true
echo
echo "All RetrievalResultCache declarations/implementations:"
rg -n 'class .*implements .*RetrievelResultCache|class .*RetrievalResultCache|interface .*RetrievalResultCache|implements .*RetrievalResultCache' . --glob '*.java' || true
echo
echo "Interface file outline/content slices:"
for f in $(fd 'RetrievalResultCache\.java$' .); do
echo "--- $f"
wc -l "$f"
ast-grep outline "$f" --view expanded || true
sed -n '1,220p' "$f"
done
echo
echo "Relevant cache implementation files:"
rg -l 'class .*implements .*RetrievalResultCache\b|RetrievalResultCache' . --glob '*.java' | while read -r file; do
echo "--- $file"
wc -l "$file"
rg -n -C5 'RetrievalResultCache|requireValidEntry|void put|Entry put|put\b|doPut|store|save|insert' "$file" || true
doneRepository: kl3inIT/OrgMemory
Length of output: 50373
Enforce requireValidEntry in the put() contract.
The validation is currently opt-in static helper; current implementations call it, but a future implementation could override put() without validation and bypass tenant scoping. Make validation mandatory before storage, e.g. via a default put(Key, Entry) that validates and delegates to an abstract/protected implementation method.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@components/graph-rag-core/src/main/java/com/orgmemory/graphrag/cache/RetrievalResultCache.java`
around lines 27 - 38, Make validation mandatory in the cache put contract by
changing the public put(Key, Entry) flow to call requireValidEntry before
storage and delegate to a separate abstract or protected implementation method.
Update existing implementations to override only that delegated storage method,
ensuring custom implementations cannot bypass tenant-scoping validation.
| fields.put( | ||
| "targetEntity", relation.targetEntity().id().toString()); | ||
| fields.put("type", relation.type()); | ||
| fields.put("keywords", String.join("\u001f", relation.keywords())); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Use unambiguous framing for nested fingerprint inputs. Delimiter concatenation is not canonical for arbitrary text: for example, ["a\u001fb"] and ["a", "b"] produce the same keyword representation. This can turn distinct curation or projection payloads into the same idempotency fingerprint.
components/graph-rag-core/src/main/java/com/orgmemory/graphrag/curation/GraphCurationFingerprint.java#L45-L45: encode each keyword with length-prefixing or structured canonical serialization.components/graph-rag-core/src/main/java/com/orgmemory/graphrag/port/GraphProjectionManifest.java#L29-L59: frame each serialized entity/relation/embedding item before combining the collection.components/graph-rag-core/src/main/java/com/orgmemory/graphrag/port/GraphProjectionManifest.java#L74-L124: replace pipe and unit-separator joins with an escaped or length-prefixed field encoding.
📍 Affects 2 files
components/graph-rag-core/src/main/java/com/orgmemory/graphrag/curation/GraphCurationFingerprint.java#L45-L45(this comment)components/graph-rag-core/src/main/java/com/orgmemory/graphrag/port/GraphProjectionManifest.java#L29-L59components/graph-rag-core/src/main/java/com/orgmemory/graphrag/port/GraphProjectionManifest.java#L74-L124
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@components/graph-rag-core/src/main/java/com/orgmemory/graphrag/curation/GraphCurationFingerprint.java`
at line 45, The fingerprint construction uses ambiguous delimiter-based
concatenation for nested values. In
components/graph-rag-core/src/main/java/com/orgmemory/graphrag/curation/GraphCurationFingerprint.java#L45-L45,
update the keyword encoding to use length-prefixing or structured canonical
serialization. In
components/graph-rag-core/src/main/java/com/orgmemory/graphrag/port/GraphProjectionManifest.java#L29-L59,
frame each serialized entity, relation, and embedding item before combining the
collection; in `#L74-L124`, replace pipe and unit-separator joins with escaped or
length-prefixed field encoding, preserving deterministic output.
| record IdentityAlias( | ||
| UUID id, | ||
| ProjectionNamespace namespace, | ||
| GraphIdentityRef source, | ||
| GraphIdentityRef target, | ||
| CurationProvenance provenance) | ||
| implements GraphCurationRecord { | ||
|
|
||
| public IdentityAlias { | ||
| Objects.requireNonNull(id, "id"); | ||
| Objects.requireNonNull(namespace, "namespace"); | ||
| Objects.requireNonNull(source, "source"); | ||
| Objects.requireNonNull(target, "target"); | ||
| Objects.requireNonNull(provenance, "provenance"); | ||
| if (source.kind() != target.kind()) { | ||
| throw new IllegalArgumentException( | ||
| "an identity alias cannot cross identity kinds"); | ||
| } | ||
| if (source.equals(target)) { | ||
| throw new IllegalArgumentException( | ||
| "an identity alias must change the identity"); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| record IdentitySuppression( | ||
| UUID id, | ||
| ProjectionNamespace namespace, | ||
| GraphIdentityRef identity, | ||
| CurationProvenance provenance) | ||
| implements GraphCurationRecord { | ||
|
|
||
| public IdentitySuppression { | ||
| Objects.requireNonNull(id, "id"); | ||
| Objects.requireNonNull(namespace, "namespace"); | ||
| Objects.requireNonNull(identity, "identity"); | ||
| Objects.requireNonNull(provenance, "provenance"); | ||
| } |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
Bind aliases and suppressions to governing evidence.
IdentityAlias and IdentitySuppression cannot be filtered by governing evidence: unlike the other variants, they carry no EvidenceReference, and GraphCurationStore.append has no separate evidence input. This permits globally applicable curation controls without a source-ACL proof. Add governing evidence to both variants, validate its organization, and persist/filter it before applying the overlay.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@components/graph-rag-core/src/main/java/com/orgmemory/graphrag/curation/GraphCurationRecord.java`
around lines 85 - 122, Update the IdentityAlias and IdentitySuppression record
definitions in GraphCurationRecord to include a required EvidenceReference
governing-evidence field, validate that reference’s organization consistently
with the other curation variants, and propagate it through all construction and
persistence paths. Update GraphCurationStore.append and overlay filtering so
each alias or suppression is persisted and applied only when its governing
evidence is allowed; do not add a separate append evidence parameter.
| private static String markdown(GraphExportDocument document) { | ||
| StringBuilder output = new StringBuilder("# Graph export\n\n## Entities\n\n"); | ||
| for (GraphExportDocument.EntityRow entity : document.entities()) { | ||
| output.append("- **").append(markdownEscape(entity.name())).append("** (`") | ||
| .append(entity.id()).append("`) — ") | ||
| .append(markdownEscape(entity.description())).append('\n'); | ||
| } | ||
| output.append("\n## Relations\n\n"); | ||
| for (GraphExportDocument.RelationRow relation : document.relations()) { | ||
| output.append("- `").append(relation.sourceEntityId()).append("` → `") | ||
| .append(relation.targetEntityId()).append("` — ") | ||
| .append(markdownEscape(relation.description())).append('\n'); | ||
| } | ||
| return output.toString(); | ||
| } | ||
|
|
||
| private static String text(GraphExportDocument document) { | ||
| StringBuilder output = new StringBuilder("ENTITIES\n"); | ||
| for (GraphExportDocument.EntityRow entity : document.entities()) { | ||
| output.append(entity.id()).append('\t').append(entity.name()).append('\t') | ||
| .append(entity.description()).append('\n'); | ||
| } | ||
| output.append("\nRELATIONS\n"); | ||
| for (GraphExportDocument.RelationRow relation : document.relations()) { | ||
| output.append(relation.id()).append('\t') | ||
| .append(relation.sourceEntityId()).append(" -> ") | ||
| .append(relation.targetEntityId()).append('\t') | ||
| .append(relation.description()).append('\n'); | ||
| } | ||
| return output.toString(); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Preserve evidence provenance in Markdown and text exports.
These branches discard every row’s evidence, so consumers of these formats cannot audit the exported graph back to its authorized source material. Render deterministic evidence references for both entities and relations, as the JSON and CSV branches do.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@components/graph-rag-core/src/main/java/com/orgmemory/graphrag/export/GraphExportFormatter.java`
around lines 84 - 114, Update the markdown and text methods to include each
EntityRow and RelationRow’s evidence references, matching the deterministic
rendering and formatting already used by the JSON and CSV export branches.
Preserve the existing fields and ordering, and render evidence for both entity
and relation rows so provenance remains auditable.
| void failedCurrentJobCanResumeWithFreshRetryBudget() { | ||
| coordinator.claimNext("worker-a", Duration.ofMinutes(5)).orElseThrow(); | ||
| while (job.getStatus() != GraphIndexJobStatus.FAILED) { | ||
| coordinator.fail( | ||
| job.getId(), | ||
| "worker-a", | ||
| "TRANSIENT", | ||
| "failure"); | ||
| if (job.getStatus() == GraphIndexJobStatus.PENDING) { | ||
| job.claim( | ||
| "worker-a", | ||
| Instant.now(), | ||
| Duration.ofMinutes(5)); | ||
| } | ||
| } | ||
|
|
||
| GraphIndexJobView resumed = | ||
| coordinator.resume(ORGANIZATION_ID, job.getId()); | ||
|
|
||
| assertEquals("PENDING", resumed.status()); | ||
| assertEquals(0, resumed.attempt()); | ||
| assertTrue(!resumed.cancellationRequested()); | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Bound the retry-until-FAILED loop.
The while (job.getStatus() != GraphIndexJobStatus.FAILED) loop has no iteration cap. If a future regression prevents the job from ever reaching FAILED, this test hangs instead of failing fast with a clear message.
🛡️ Proposed fix: cap iterations
- while (job.getStatus() != GraphIndexJobStatus.FAILED) {
+ int guard = 0;
+ while (job.getStatus() != GraphIndexJobStatus.FAILED) {
+ if (guard++ > 10) {
+ fail("Job never reached FAILED status");
+ }
coordinator.fail(
job.getId(),
"worker-a",
"TRANSIENT",
"failure");📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| void failedCurrentJobCanResumeWithFreshRetryBudget() { | |
| coordinator.claimNext("worker-a", Duration.ofMinutes(5)).orElseThrow(); | |
| while (job.getStatus() != GraphIndexJobStatus.FAILED) { | |
| coordinator.fail( | |
| job.getId(), | |
| "worker-a", | |
| "TRANSIENT", | |
| "failure"); | |
| if (job.getStatus() == GraphIndexJobStatus.PENDING) { | |
| job.claim( | |
| "worker-a", | |
| Instant.now(), | |
| Duration.ofMinutes(5)); | |
| } | |
| } | |
| GraphIndexJobView resumed = | |
| coordinator.resume(ORGANIZATION_ID, job.getId()); | |
| assertEquals("PENDING", resumed.status()); | |
| assertEquals(0, resumed.attempt()); | |
| assertTrue(!resumed.cancellationRequested()); | |
| } | |
| void failedCurrentJobCanResumeWithFreshRetryBudget() { | |
| coordinator.claimNext("worker-a", Duration.ofMinutes(5)).orElseThrow(); | |
| int guard = 0; | |
| while (job.getStatus() != GraphIndexJobStatus.FAILED) { | |
| if (guard++ > 10) { | |
| fail("Job never reached FAILED status"); | |
| } | |
| coordinator.fail( | |
| job.getId(), | |
| "worker-a", | |
| "TRANSIENT", | |
| "failure"); | |
| if (job.getStatus() == GraphIndexJobStatus.PENDING) { | |
| job.claim( | |
| "worker-a", | |
| Instant.now(), | |
| Duration.ofMinutes(5)); | |
| } | |
| } | |
| GraphIndexJobView resumed = | |
| coordinator.resume(ORGANIZATION_ID, job.getId()); | |
| assertEquals("PENDING", resumed.status()); | |
| assertEquals(0, resumed.attempt()); | |
| assertTrue(!resumed.cancellationRequested()); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@core/src/test/java/com/orgmemory/core/knowledge/GraphIndexingCoordinatorTests.java`
around lines 212 - 234, Bound the retry loop in
failedCurrentJobCanResumeWithFreshRetryBudget with a finite iteration cap, such
as a for-loop or explicit counter, while preserving the existing
fail-and-reclaim behavior. Ensure the test fails clearly if job.getStatus()
never reaches GraphIndexJobStatus.FAILED instead of hanging indefinitely.
| @Test | ||
| void deleteRetiresCanonicalLedgerThenRemovesDerivedGraphAndCaches() { | ||
| when(authorization.check(any())) | ||
| .thenReturn(AuthorizationDecision.allow("model-v1")); | ||
|
|
||
| service.delete(actor, ASSET_ID); | ||
|
|
||
| verify(ingestion).retire(ORGANIZATION_ID, ASSET_ID); | ||
| verify(graph).removeRevision(ORGANIZATION_ID, REVISION_ID); | ||
| verify(modelCache).invalidate(any()); | ||
| verify(retrievalCache).invalidateNamespace(any()); | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win
Verify the retire→remove→invalidate sequence, not just that each call happened.
Plain verify(...) doesn't confirm ingestion.retire completes before graph.removeRevision/cache invalidation run. If lifecycle ordering ever regresses, this test would still pass.
♻️ Proposed fix: assert sequencing with inOrder
service.delete(actor, ASSET_ID);
- verify(ingestion).retire(ORGANIZATION_ID, ASSET_ID);
- verify(graph).removeRevision(ORGANIZATION_ID, REVISION_ID);
- verify(modelCache).invalidate(any());
- verify(retrievalCache).invalidateNamespace(any());
+ var order = org.mockito.Mockito.inOrder(ingestion, graph, modelCache, retrievalCache);
+ order.verify(ingestion).retire(ORGANIZATION_ID, ASSET_ID);
+ order.verify(graph).removeRevision(ORGANIZATION_ID, REVISION_ID);
+ order.verify(modelCache).invalidate(any());
+ order.verify(retrievalCache).invalidateNamespace(any());📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @Test | |
| void deleteRetiresCanonicalLedgerThenRemovesDerivedGraphAndCaches() { | |
| when(authorization.check(any())) | |
| .thenReturn(AuthorizationDecision.allow("model-v1")); | |
| service.delete(actor, ASSET_ID); | |
| verify(ingestion).retire(ORGANIZATION_ID, ASSET_ID); | |
| verify(graph).removeRevision(ORGANIZATION_ID, REVISION_ID); | |
| verify(modelCache).invalidate(any()); | |
| verify(retrievalCache).invalidateNamespace(any()); | |
| } | |
| `@Test` | |
| void deleteRetiresCanonicalLedgerThenRemovesDerivedGraphAndCaches() { | |
| when(authorization.check(any())) | |
| .thenReturn(AuthorizationDecision.allow("model-v1")); | |
| service.delete(actor, ASSET_ID); | |
| var order = org.mockito.Mockito.inOrder(ingestion, graph, modelCache, retrievalCache); | |
| order.verify(ingestion).retire(ORGANIZATION_ID, ASSET_ID); | |
| order.verify(graph).removeRevision(ORGANIZATION_ID, REVISION_ID); | |
| order.verify(modelCache).invalidate(any()); | |
| order.verify(retrievalCache).invalidateNamespace(any()); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@core/src/test/java/com/orgmemory/core/knowledge/KnowledgeAssetLifecycleServiceTests.java`
around lines 79 - 90, Update
deleteRetiresCanonicalLedgerThenRemovesDerivedGraphAndCaches to verify the
lifecycle calls with an InOrder verifier, asserting ingestion.retire occurs
before graph.removeRevision and both cache invalidations. Keep the existing
authorization setup and call arguments, but replace the independent interaction
checks with ordered verification.
| - name: Graph management is explicit and resource scoped | ||
| check: | ||
| - user: user:laura | ||
| object: knowledge_space:finance | ||
| assertions: | ||
| can_curate_graph: true | ||
| can_export_graph: true | ||
| - user: user:minh | ||
| object: knowledge_space:finance | ||
| assertions: | ||
| can_curate_graph: false | ||
| can_export_graph: false | ||
| - user: user:laura | ||
| object: knowledge_asset:finance-private-draft | ||
| assertions: | ||
| can_delete: true | ||
| can_rebuild: true | ||
| - user: user:minh | ||
| object: knowledge_asset:finance-private-draft | ||
| assertions: | ||
| can_delete: false | ||
| can_rebuild: false | ||
|
|
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Add ListObjects coverage for the new permissions.
This adds negative Check cases, but no list_objects assertions for can_curate_graph, can_export_graph, can_delete, or can_rebuild. Add positive and empty-result cases for Laura and Minh so object enumeration cannot broaden access.
As per path instructions, “Require negative Check and ListObjects coverage for every new permission path.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@integrations/authorization-openfga/src/test/openfga/store.fga.yaml` around
lines 189 - 211, Extend the “Graph management is explicit and resource scoped”
test block with ListObjects assertions for can_curate_graph, can_export_graph,
can_delete, and can_rebuild. Add positive enumeration cases for Laura and
empty-result cases for Minh, covering both knowledge_space and knowledge_asset
resources without changing the existing Check assertions.
Source: Path instructions
| return transactions.execute(status -> { | ||
| String fingerprint = GraphCurationFingerprint.fingerprint(record); | ||
| GraphCurationRecord existing = | ||
| existing(record.namespace(), normalizedKey, fingerprint); | ||
| if (existing != null) { | ||
| return existing; | ||
| } | ||
| validateIdentities(record); | ||
| insert(normalizedKey, fingerprint, record); | ||
| return record; | ||
| }); | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Idempotent append is not concurrency-safe on first insert.
existing(...) uses FOR UPDATE, but when no row yet exists there is nothing to lock. Two concurrent requests with the same idempotencyKey can both observe existing == null and both insert(...); the second write then fails the UNIQUE(organization_id, workspace, collection_name, idempotency_key) constraint (V33 L212-217) with a raw DataIntegrityViolationException instead of returning the stored record or a CurationConflictException. A retried identical curation therefore surfaces a 500 rather than an idempotent success.
Consider catching the duplicate-key violation and re-reading via existing(...) to preserve idempotency semantics.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@integrations/graph-rag-postgres/src/main/java/com/orgmemory/graphrag/postgres/PostgresGraphCurationStore.java`
around lines 44 - 55, The transaction in the idempotent append flow must handle
concurrent first inserts: update the logic around existing(...) and insert(...)
to catch a duplicate-key DataIntegrityViolationException, re-read the record
with existing(...), and return it when the fingerprint matches or raise
CurationConflictException for a conflicting record. Preserve the current
validation and normal insert behavior.
| @Override | ||
| public Optional<ModelInvocationCache.Entry> get( | ||
| ModelInvocationCache.Key key, Instant now) { | ||
| Objects.requireNonNull(key, "key"); | ||
| Objects.requireNonNull(now, "now"); | ||
| List<ModelInvocationCache.Entry> entries = jdbc.query(""" | ||
| SELECT media_type, payload, created_at, expires_at | ||
| FROM graph_model_invocation_cache | ||
| WHERE organization_id = :organizationId | ||
| AND workspace = :workspace | ||
| AND collection_name = :collection | ||
| AND operation = :operation | ||
| AND input_hash = :inputHash | ||
| AND model_route_fingerprint = :modelRouteFingerprint | ||
| AND profile_fingerprint = :profileFingerprint | ||
| AND expires_at > :now | ||
| """, | ||
| modelKeyParameters(key).addValue("now", Timestamp.from(now)), | ||
| (resultSet, rowNumber) -> new ModelInvocationCache.Entry( | ||
| resultSet.getString("media_type"), | ||
| resultSet.getString("payload"), | ||
| resultSet.getTimestamp("created_at").toInstant(), | ||
| resultSet.getTimestamp("expires_at").toInstant())); | ||
| return entries.stream().findFirst(); | ||
| } | ||
|
|
||
| @Override | ||
| public void put( | ||
| ModelInvocationCache.Key key, ModelInvocationCache.Entry entry) { | ||
| Objects.requireNonNull(key, "key"); | ||
| Objects.requireNonNull(entry, "entry"); | ||
| MapSqlParameterSource parameters = modelKeyParameters(key) | ||
| .addValue("mediaType", entry.mediaType()) | ||
| .addValue("payload", entry.payload()) | ||
| .addValue("createdAt", Timestamp.from(entry.createdAt())) | ||
| .addValue("expiresAt", Timestamp.from(entry.expiresAt())); | ||
| jdbc.update(""" | ||
| INSERT INTO graph_model_invocation_cache ( | ||
| organization_id, | ||
| workspace, | ||
| collection_name, | ||
| operation, | ||
| input_hash, | ||
| model_route_fingerprint, | ||
| profile_fingerprint, | ||
| media_type, | ||
| payload, | ||
| created_at, | ||
| expires_at | ||
| ) | ||
| VALUES ( | ||
| :organizationId, | ||
| :workspace, | ||
| :collection, | ||
| :operation, | ||
| :inputHash, | ||
| :modelRouteFingerprint, | ||
| :profileFingerprint, | ||
| :mediaType, | ||
| :payload, | ||
| :createdAt, | ||
| :expiresAt | ||
| ) | ||
| ON CONFLICT ( | ||
| organization_id, | ||
| workspace, | ||
| collection_name, | ||
| operation, | ||
| input_hash, | ||
| model_route_fingerprint, | ||
| profile_fingerprint | ||
| ) | ||
| DO UPDATE SET | ||
| media_type = excluded.media_type, | ||
| payload = excluded.payload, | ||
| created_at = excluded.created_at, | ||
| expires_at = excluded.expires_at | ||
| """, parameters); | ||
| } | ||
|
|
||
| @Override | ||
| public void invalidate(ProjectionNamespace namespace) { | ||
| MapSqlParameterSource parameters = namespaceParameters(namespace); | ||
| jdbc.update(""" | ||
| DELETE FROM graph_model_invocation_cache | ||
| WHERE organization_id = :organizationId | ||
| AND workspace = :workspace | ||
| AND collection_name = :collection | ||
| """, parameters); | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
LGTM on the remaining cache logic (ModelInvocationCache read/write/invalidate, evidence upsert-in-transaction, key/parameter builders).
One operational note: expired rows in graph_model_invocation_cache/graph_retrieval_result_cache are only filtered at read time (expires_at > :now) and never proactively purged; over time both tables will grow unbounded except when a namespace is explicitly invalidated. Consider a scheduled cleanup job.
Also applies to: 190-399
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@integrations/graph-rag-postgres/src/main/java/com/orgmemory/graphrag/postgres/PostgresGraphRagCacheStore.java`
around lines 39 - 128, Implement scheduled cleanup for expired rows in both
graph_model_invocation_cache and graph_retrieval_result_cache. Add a maintenance
method near the existing ModelInvocationCache read/write/invalidate logic that
deletes rows where expires_at is at or before the current time, and register it
with the application’s established scheduling mechanism; preserve namespace
invalidation behavior and use the existing JDBC access patterns.
What changed
Why
PR6 closes the LightRAG v1.5.4 lifecycle/cache parity slice while preserving OrgMemory's stricter evidence-level authorization. Derived graph rows and caches remain rebuildable and cannot grant access.
Validation
./gradlew.bat --no-daemon clean buildpnpm -C web check:apipnpm -C web typecheckpnpm -C web buildTarget is intentionally
light-rag; no LightRAG program PR targetsmaindirectly.Summary by CodeRabbit
New Features
Bug Fixes
Tests