Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,9 +169,12 @@ evidence snapshot and exact current governing-evidence decision through the
Retrieval-owned `GraphEvidenceVerifier`; Graph does not import Retrieval scope
resolution, candidate, or store implementation types. Verified snapshots reject
unknown Knowledge Spaces, and canonical evidence rechecks carry only the assets
authorized for the requested Space. Retrieval remains
explicitly open while its remaining sibling adapters are replaced by
intentional APIs. The
authorized for the requested Space. API and Worker inject interfaces for the
canonical/GraphRAG engines, citation/source opening, authorization inspection,
and embedding-profile resolution; full evidence-scope resolution plus the
default and JDBC implementations are package-private. Retrieval remains explicitly open while
its remaining concrete/persistence root types are internalized and its final
dependency allowlist is proven. The
provider-neutral object-storage port is exposed as the
`knowledge::storage` named interface. Leased database jobs carry ingestion work
across processes. A specific Knowledge Asset
Expand Down
1 change: 1 addition & 0 deletions apps/api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ dependencies {
testImplementation("org.springframework.boot:spring-boot-starter-webmvc-test")
testImplementation("org.springframework.security:spring-security-test")
testImplementation("org.springframework.boot:spring-boot-testcontainers")
testImplementation("org.springframework.modulith:spring-modulith-starter-test")
testImplementation("io.projectreactor:reactor-test")
testImplementation("org.testcontainers:testcontainers-junit-jupiter")
testImplementation("org.testcontainers:testcontainers-postgresql")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import com.orgmemory.core.organization.AppUser;
import com.orgmemory.core.organization.CurrentActor;
import com.orgmemory.core.knowledge.retrieval.AuthorizationResourceDirectory;
import com.orgmemory.core.knowledge.retrieval.KnowledgeEvidenceScopeResolver;
import com.orgmemory.core.knowledge.retrieval.KnowledgeAssetAccessInspector;
import io.swagger.v3.oas.annotations.Operation;
import java.time.Instant;
import java.util.LinkedHashMap;
Expand Down Expand Up @@ -64,7 +64,7 @@ class AdminPermissionController {
private final AdminAccessGuard guard;
private final AccessExplanationService explanations;
private final AuthorizationResourceDirectory resources;
private final KnowledgeEvidenceScopeResolver evidenceScopes;
private final KnowledgeAssetAccessInspector evidenceScopes;
private final KnowledgeAssetRepository assets;
private final KnowledgeAssetVersionRepository versions;
private final KnowledgeSpaceQuery spaces;
Expand All @@ -73,7 +73,7 @@ class AdminPermissionController {
AdminAccessGuard guard,
AccessExplanationService explanations,
AuthorizationResourceDirectory resources,
KnowledgeEvidenceScopeResolver evidenceScopes,
KnowledgeAssetAccessInspector evidenceScopes,
KnowledgeAssetRepository assets,
KnowledgeAssetVersionRepository versions,
KnowledgeSpaceQuery spaces) {
Expand Down Expand Up @@ -211,7 +211,7 @@ private ExplainAccessResponse canonicalContentResponse(
contentState = AccessState.UNKNOWN;
contentReason = "NOT_EVALUATED_RELATIONSHIP_NOT_ALLOWED";
} else {
KnowledgeEvidenceScopeResolver.AssetInspection content = evidenceScopes.inspectAsset(
KnowledgeAssetAccessInspector.AssetInspection content = evidenceScopes.inspectAsset(
subject,
assetId,
relationship.policyVersion(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.orgmemory.api;

import static org.junit.jupiter.api.Assertions.assertEquals;

import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.core.importer.ImportOption;
import java.util.Set;
import java.util.TreeSet;
import org.junit.jupiter.api.Test;

class RetrievalAdapterBoundaryTests {

@Test
void apiDependsOnlyOnIntentionalRetrievalContracts() {
var dependencies = new ClassFileImporter()
.withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
.importPackages("com.orgmemory.api")
.stream()
.flatMap(type -> type.getDirectDependenciesFromSelf().stream())
.map(dependency -> dependency.getTargetClass().getName())
.filter(name -> name.startsWith("com.orgmemory.core.knowledge.retrieval."))
.collect(TreeSet::new, Set::add, Set::addAll);

assertEquals(
Set.of(
"com.orgmemory.core.knowledge.retrieval.AuthorizationResourceDirectory",
"com.orgmemory.core.knowledge.retrieval.CanonicalHybridKnowledgeSearch",
"com.orgmemory.core.knowledge.retrieval.CitationContent",
"com.orgmemory.core.knowledge.retrieval.CitationContentService",
"com.orgmemory.core.knowledge.retrieval.EmbeddingDistanceMetric",
"com.orgmemory.core.knowledge.retrieval.EmbeddingProfileRef",
"com.orgmemory.core.knowledge.retrieval.EmbeddingProfileRegistry",
"com.orgmemory.core.knowledge.retrieval.EmbeddingProfileSpec",
"com.orgmemory.core.knowledge.retrieval.GraphRagKnowledgeRetrievalService",
"com.orgmemory.core.knowledge.retrieval.GraphRagRetrievalPolicy",
"com.orgmemory.core.knowledge.retrieval.GraphRagRetrievalPolicy$RerankPolicy",
"com.orgmemory.core.knowledge.retrieval.KnowledgeAssetAccessInspector",
"com.orgmemory.core.knowledge.retrieval.KnowledgeAssetAccessInspector$AssetInspection",
"com.orgmemory.core.knowledge.retrieval.KnowledgeEmbeddingProperties",
"com.orgmemory.core.knowledge.retrieval.KnowledgeRetrievalProperties",
"com.orgmemory.core.knowledge.retrieval.QueryEmbedding",
"com.orgmemory.core.knowledge.retrieval.QueryEmbeddingPort",
"com.orgmemory.core.knowledge.retrieval.SourceContent",
"com.orgmemory.core.knowledge.retrieval.SourceContentService"),
dependencies);
}
}
1 change: 1 addition & 0 deletions apps/worker/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ dependencies {
runtimeOnly("org.postgresql:postgresql")

testImplementation("org.junit.jupiter:junit-jupiter")
testImplementation("org.springframework.modulith:spring-modulith-starter-test")
testImplementation(libs.apache.poi.ooxml)
testImplementation("org.springframework.boot:spring-boot-starter-webmvc-test")
testImplementation("org.springframework.boot:spring-boot-testcontainers")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import com.orgmemory.core.knowledge.sourceledger.SourceIngestionProperties;

import com.orgmemory.core.knowledge.retrieval.CanonicalHybridKnowledgeSearch;
import com.orgmemory.core.knowledge.retrieval.CanonicalHybridKnowledgeSearchConfiguration;
import com.orgmemory.core.knowledge.graph.GraphProcessingProperties;
import com.orgmemory.core.knowledge.graph.KnowledgeGraphExplorerConfiguration;
import com.orgmemory.core.knowledge.retrieval.KnowledgeRetrievalProperties;
Expand Down Expand Up @@ -51,7 +51,7 @@
excludeFilters = @ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE,
classes = {
CanonicalHybridKnowledgeSearch.class,
CanonicalHybridKnowledgeSearchConfiguration.class,
KnowledgeGraphExplorerConfiguration.class
}))
public class OrgMemoryWorkerApplication {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.orgmemory.worker;

import static org.junit.jupiter.api.Assertions.assertEquals;

import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.core.importer.ImportOption;
import java.util.Set;
import java.util.TreeSet;
import org.junit.jupiter.api.Test;

class RetrievalAdapterBoundaryTests {

@Test
void workerDependsOnlyOnIntentionalRetrievalContracts() {
var dependencies = new ClassFileImporter()
.withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
.importPackages("com.orgmemory.worker")
.stream()
.flatMap(type -> type.getDirectDependenciesFromSelf().stream())
.map(dependency -> dependency.getTargetClass().getName())
.filter(name -> name.startsWith("com.orgmemory.core.knowledge.retrieval."))
.collect(TreeSet::new, Set::add, Set::addAll);

assertEquals(
Set.of(
"com.orgmemory.core.knowledge.retrieval.CanonicalHybridKnowledgeSearchConfiguration",
"com.orgmemory.core.knowledge.retrieval.EmbeddingDistanceMetric",
"com.orgmemory.core.knowledge.retrieval.EmbeddingProfileRef",
"com.orgmemory.core.knowledge.retrieval.EmbeddingProfileRegistry",
"com.orgmemory.core.knowledge.retrieval.EmbeddingProfileSpec",
"com.orgmemory.core.knowledge.retrieval.KnowledgeRetrievalProperties"),
dependencies);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import com.orgmemory.core.knowledge.connector.ConnectorMembershipMember;
import com.orgmemory.core.knowledge.connector.ConnectorPermissionItem;
import com.orgmemory.core.knowledge.retrieval.CanonicalHybridKnowledgeSearch;
import com.orgmemory.core.knowledge.retrieval.CanonicalHybridKnowledgeSearchConfiguration;
import com.orgmemory.core.knowledge.retrieval.KnowledgeRetrievalProperties;
import com.orgmemory.core.knowledge.connector.ConnectorCaptureStatus;
import com.orgmemory.core.knowledge.retrieval.QueryEmbeddingPort;
Expand Down Expand Up @@ -88,7 +89,7 @@
"orgmemory.graph-rag.postgres.apache-age-mode=disabled",
"orgmemory.connector.scheduling-enabled=false"
})
@Import(CanonicalHybridKnowledgeSearch.class)
@Import(CanonicalHybridKnowledgeSearchConfiguration.class)
@EnableConfigurationProperties(KnowledgeRetrievalProperties.class)
@Testcontainers
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import com.orgmemory.core.knowledge.connector.ConnectorMembershipMember;
import com.orgmemory.core.knowledge.connector.ConnectorPermissionItem;
import com.orgmemory.core.knowledge.retrieval.CanonicalHybridKnowledgeSearch;
import com.orgmemory.core.knowledge.retrieval.CanonicalHybridKnowledgeSearchConfiguration;
import com.orgmemory.core.knowledge.retrieval.KnowledgeRetrievalProperties;
import com.orgmemory.core.knowledge.connector.ConnectorCaptureStatus;
import com.orgmemory.core.knowledge.retrieval.QueryEmbeddingPort;
Expand Down Expand Up @@ -84,7 +85,7 @@
"orgmemory.graph-rag.postgres.apache-age-mode=disabled",
"orgmemory.connector.scheduling-enabled=false"
})
@Import(CanonicalHybridKnowledgeSearch.class)
@Import(CanonicalHybridKnowledgeSearchConfiguration.class)
@EnableConfigurationProperties(KnowledgeRetrievalProperties.class)
@Testcontainers
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import com.orgmemory.core.knowledge.connector.ConnectorMembershipMember;
import com.orgmemory.core.knowledge.connector.ConnectorPermissionItem;
import com.orgmemory.core.knowledge.retrieval.CanonicalHybridKnowledgeSearch;
import com.orgmemory.core.knowledge.retrieval.CanonicalHybridKnowledgeSearchConfiguration;
import com.orgmemory.core.knowledge.retrieval.KnowledgeRetrievalProperties;
import com.orgmemory.core.knowledge.connector.ConnectorCaptureStatus;
import com.orgmemory.core.knowledge.retrieval.QueryEmbeddingPort;
Expand Down Expand Up @@ -81,7 +82,7 @@
"orgmemory.graph-rag.postgres.apache-age-mode=disabled",
"orgmemory.connector.scheduling-enabled=false"
})
@Import(CanonicalHybridKnowledgeSearch.class)
@Import(CanonicalHybridKnowledgeSearchConfiguration.class)
@EnableConfigurationProperties(KnowledgeRetrievalProperties.class)
@Testcontainers
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.orgmemory.core.authorization.RelationshipTupleWriteResult;
import com.orgmemory.core.authorization.ResourceRef;
import com.orgmemory.core.knowledge.retrieval.CanonicalHybridKnowledgeSearch;
import com.orgmemory.core.knowledge.retrieval.CanonicalHybridKnowledgeSearchConfiguration;
import com.orgmemory.core.knowledge.connector.ConnectorCrawlBatch;
import com.orgmemory.core.knowledge.connector.ConnectorIngestionResult;
import com.orgmemory.core.knowledge.connector.ConnectorIngestionService;
Expand Down Expand Up @@ -76,7 +77,7 @@
"orgmemory.graph-rag.postgres.apache-age-mode=disabled",
"orgmemory.connector.scheduling-enabled=false"
})
@Import(CanonicalHybridKnowledgeSearch.class)
@Import(CanonicalHybridKnowledgeSearchConfiguration.class)
@EnableConfigurationProperties(KnowledgeRetrievalProperties.class)
@Testcontainers
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import com.orgmemory.core.knowledge.retrieval.QueryEmbedding;
import com.orgmemory.core.knowledge.retrieval.KnowledgeRetrievalProperties;
import com.orgmemory.core.knowledge.retrieval.CanonicalHybridKnowledgeSearch;
import com.orgmemory.core.knowledge.retrieval.CanonicalHybridKnowledgeSearchConfiguration;
import com.orgmemory.core.knowledge.storage.ObjectContent;
import com.orgmemory.core.knowledge.storage.ObjectStoragePort;
import com.orgmemory.core.knowledge.storage.ObjectWriteRequest;
Expand Down Expand Up @@ -86,7 +87,7 @@
})
@Import({
SourceIngestionPipelineIntegrationTests.UploadTestConfiguration.class,
CanonicalHybridKnowledgeSearch.class
CanonicalHybridKnowledgeSearchConfiguration.class
})
@EnableConfigurationProperties(KnowledgeRetrievalProperties.class)
@Testcontainers
Expand Down
Original file line number Diff line number Diff line change
@@ -1,60 +1,13 @@
package com.orgmemory.core.knowledge.retrieval;

import com.orgmemory.core.shared.error.KnowledgeResourceNotFoundException;

import com.orgmemory.core.knowledge.asset.KnowledgeAssetRetrievalQuery;

import com.orgmemory.core.authorization.ResourceRef;
import com.orgmemory.core.knowledge.space.KnowledgeSpaceQuery;
import com.orgmemory.core.organization.OrganizationResourceQuery;
import java.util.Objects;
import java.util.UUID;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
* Resolves an administrator-supplied authorization resource against the
* canonical tenant directory before OpenFGA is queried.
* Adapter-facing query that validates an authorization resource against its
* canonical tenant-owned directory before policy evaluation.
*/
@Service
public class AuthorizationResourceDirectory {

private final OrganizationResourceQuery organizationResources;
private final KnowledgeSpaceQuery spaces;
private final KnowledgeAssetRetrievalQuery assets;

AuthorizationResourceDirectory(
OrganizationResourceQuery organizationResources,
KnowledgeSpaceQuery spaces,
KnowledgeAssetRetrievalQuery assets) {
this.organizationResources = organizationResources;
this.spaces = spaces;
this.assets = assets;
}
public interface AuthorizationResourceDirectory {

@Transactional(readOnly = true)
public ResourceRef require(
UUID organizationId,
String resourceType,
UUID resourceId) {
Objects.requireNonNull(organizationId, "organizationId");
Objects.requireNonNull(resourceId, "resourceId");
String type = Objects.requireNonNull(resourceType, "resourceType").strip();
boolean exists = switch (type) {
case "organization" ->
organizationId.equals(resourceId)
&& organizationResources.organizationExists(organizationId);
case "organizational_unit" ->
organizationResources.departmentExists(organizationId, resourceId);
case "knowledge_space" ->
spaces.exists(organizationId, resourceId);
case "knowledge_asset" ->
assets.exists(organizationId, resourceId);
default -> false;
};
if (!exists) {
throw new KnowledgeResourceNotFoundException();
}
return ResourceRef.of(organizationId, type, resourceId);
}
ResourceRef require(UUID organizationId, String resourceType, UUID resourceId);
}
Loading