relationship persistence, context entities, idempotent asset creation, audit/version handling for relationship changes

This commit is contained in:
2026-05-06 02:09:23 +02:00
parent bf59087073
commit 286ebc3cb6
12 changed files with 651 additions and 24 deletions

View File

@@ -9,6 +9,8 @@ from kontextual_engine import (
AssetRepresentation,
AuthorizationError,
Classification,
ContextEntity,
ContextEntityType,
InMemoryAssetRegistryRepository,
LifecycleState,
MetadataRecord,
@@ -93,6 +95,86 @@ def test_asset_registry_lifecycle_policy_denial_fails_closed_and_audits() -> Non
assert repo.get_asset("asset-governed").lifecycle == LifecycleState.ACTIVE
def test_asset_registry_create_is_idempotent_for_same_key_and_payload() -> None:
repo = InMemoryAssetRegistryRepository()
service = AssetRegistryService(repo)
context = operation_context()
classification = Classification(asset_type="document", sensitivity=Sensitivity.PUBLIC)
source_ref = SourceReference(source_system="repo", path="README.md")
representation = AssetRepresentation.from_content(
"asset-readme",
RepresentationKind.SOURCE,
"text/markdown",
"# Readme\n",
)
first = service.create_asset(
"Readme",
classification,
context,
asset_id="asset-readme",
source_refs=[source_ref],
representations=[representation],
idempotency_key="create-readme",
)
second = service.create_asset(
"Readme",
classification,
context,
asset_id="asset-readme",
source_refs=[source_ref],
representations=[representation],
idempotency_key="create-readme",
)
assert second.asset.id == first.asset.id
assert second.version.version_id == first.version.version_id
assert second.audit_event.event_id == first.audit_event.event_id
assert len(repo.list_versions("asset-readme")) == 1
assert len(repo.list_audit_events(target="asset:asset-readme")) == 1
with pytest.raises(ValidationError, match="Idempotency key"):
service.create_asset(
"Readme renamed",
classification,
context,
asset_id="asset-readme",
source_refs=[source_ref],
representations=[representation],
idempotency_key="create-readme",
)
def test_asset_registry_relationships_create_versions_and_audit() -> None:
repo = InMemoryAssetRegistryRepository()
service = AssetRegistryService(repo)
context = operation_context()
classification = Classification(asset_type="document", sensitivity=Sensitivity.INTERNAL)
source = service.create_asset("Source", classification, context, asset_id="asset-source")
target = service.create_asset("Target", classification, context, asset_id="asset-target")
result = service.link_asset_to_asset(
source.asset.id,
target.asset.id,
"depends_on",
context,
confidence=0.91,
provenance={"producer": "test"},
)
relationships = repo.list_relationships(source_id=source.asset.id)
versions = repo.list_versions(source.asset.id)
assert relationships == [result.relationship]
assert result.relationship.target_id == target.asset.id
assert result.relationship.confidence == 0.91
assert versions[-1].change_type.value == "relationship_changed"
assert versions[-1].relationship_delta["added"]["predicate"] == "depends_on"
assert repo.get_asset(source.asset.id).current_version_id == result.version.version_id
assert repo.get_asset(target.asset.id).current_version_id == target.version.version_id
assert repo.list_audit_events(target=f"asset:{source.asset.id}")[-1].operation == "asset.relationship.add"
def test_sqlite_asset_registry_survives_reinstantiation(tmp_path: Path) -> None:
db_path = tmp_path / "registry.sqlite"
repo = SQLiteAssetRegistryRepository(db_path)
@@ -138,6 +220,42 @@ def test_sqlite_asset_registry_survives_reinstantiation(tmp_path: Path) -> None:
]
def test_sqlite_registry_persists_context_entities_relationships_and_idempotency(tmp_path: Path) -> None:
db_path = tmp_path / "registry.sqlite"
repo = SQLiteAssetRegistryRepository(db_path)
service = AssetRegistryService(repo)
context = operation_context()
created = service.create_asset(
"Knowledge Policy",
Classification(asset_type="policy", sensitivity=Sensitivity.INTERNAL),
context,
asset_id="asset-policy",
idempotency_key="create-policy",
)
entity = ContextEntity(
entity_type=ContextEntityType.PROJECT,
name="Kontextual Engine",
entity_id="entity-kontextual",
)
linked = service.link_asset_to_context_entity(
created.asset.id,
entity,
"about_project",
context,
)
reloaded = SQLiteAssetRegistryRepository(db_path)
assert reloaded.get_idempotency_record("create-policy").result_refs["asset_id"] == "asset-policy"
assert reloaded.list_context_entities()[0].entity_id == "entity-kontextual"
assert reloaded.list_relationships(source_id="asset-policy")[0].relationship_id == linked.relationship.relationship_id
assert reloaded.list_versions("asset-policy")[-1].relationship_delta["added"]["target_kind"] == "context_entity"
assert [event.operation for event in reloaded.list_audit_events(target="asset:asset-policy")] == [
"asset.create",
"asset.relationship.add",
]
def test_sqlite_registry_enforces_representation_asset_reference(tmp_path: Path) -> None:
repo = SQLiteAssetRegistryRepository(tmp_path / "registry.sqlite")
representation = AssetRepresentation.from_content(
@@ -179,4 +297,3 @@ class DenyLifecyclePolicy:
context={"resource_metadata": resource_metadata or {}},
)
return PolicyDecision.allow(context.actor.id, action, resource)