from pathlib import Path import pytest from kontextual_engine import ( Actor, ActorType, AssetRegistryService, AssetRepresentation, AuthorizationError, Classification, InMemoryAssetRegistryRepository, LifecycleState, MetadataRecord, OperationContext, PolicyDecision, RepresentationKind, Sensitivity, SourceReference, SQLiteAssetRegistryRepository, ValidationError, ) def test_asset_registry_service_creates_assets_with_versions_and_audit() -> None: repo = InMemoryAssetRegistryRepository() service = AssetRegistryService(repo) context = operation_context() source_ref = SourceReference( source_system="repo", path="docs/intent.md", checksum="sha256:source", ) representation = AssetRepresentation.from_content( "asset-intent", RepresentationKind.SOURCE, "text/markdown", "# Intent\n", storage_ref="object://intent-source", source_ref_id=source_ref.id, ) metadata = MetadataRecord( "topic", "architecture", provenance={"producer": "human"}, confirmed=True, ) result = service.create_asset( "Intent", Classification( asset_type="document", sensitivity=Sensitivity.INTERNAL, owner="Platform Knowledge", ), context, asset_id="asset-intent", source_refs=[source_ref], representations=[representation], metadata_records=[metadata], ) assert result.asset.id == "asset-intent" assert result.asset.current_version_id == result.version.version_id assert result.version.sequence == 1 assert result.audit_event.outcome.value == "success" assert result.policy_decision.allowed is True assert repo.get_asset("asset-intent").source_refs[0].path == "docs/intent.md" assert repo.list_representations(asset_id="asset-intent")[0].storage_ref == "object://intent-source" assert repo.list_metadata_records("asset-intent")[0].confirmed is True assert repo.list_audit_events(target="asset:asset-intent")[0].operation == "asset.create" def test_asset_registry_lifecycle_policy_denial_fails_closed_and_audits() -> None: repo = InMemoryAssetRegistryRepository() service = AssetRegistryService(repo, policy_gateway=DenyLifecyclePolicy()) context = operation_context() created = service.create_asset( "Governed Asset", Classification(asset_type="document", sensitivity=Sensitivity.CONFIDENTIAL), context, asset_id="asset-governed", ) with pytest.raises(AuthorizationError) as exc_info: service.transition_lifecycle(created.asset.id, LifecycleState.RETIRED, context) events = repo.list_audit_events(target="asset:asset-governed") assert exc_info.value.details["correlation_id"] == "corr-test" assert exc_info.value.details["policy_decision"]["effect"] == "fail_closed" assert [event.outcome.value for event in events] == ["success", "denied"] assert repo.get_asset("asset-governed").lifecycle == LifecycleState.ACTIVE def test_sqlite_asset_registry_survives_reinstantiation(tmp_path: Path) -> None: db_path = tmp_path / "registry.sqlite" repo = SQLiteAssetRegistryRepository(db_path) service = AssetRegistryService(repo) context = operation_context() source_ref = SourceReference(source_system="repo", path="README.md", checksum="sha256:readme") source = AssetRepresentation.from_content( "asset-readme", RepresentationKind.SOURCE, "text/markdown", "# Readme\n", storage_ref="object://readme-source", ) created = service.create_asset( "Readme", Classification(asset_type="document", sensitivity=Sensitivity.PUBLIC), context, asset_id="asset-readme", source_refs=[source_ref], representations=[source], ) service.add_metadata_record( created.asset.id, MetadataRecord("owner", "Platform Knowledge", confirmed=True), context, ) service.request_delete(created.asset.id, context) reloaded = SQLiteAssetRegistryRepository(db_path) asset = reloaded.get_asset("asset-readme") assert asset.lifecycle == LifecycleState.DELETE_REQUESTED assert asset.source_refs[0].path == "README.md" assert [item.kind for item in reloaded.list_representations(asset_id=asset.id)] == [ RepresentationKind.SOURCE ] assert [item.key for item in reloaded.list_metadata_records(asset.id)] == ["owner"] assert [version.sequence for version in reloaded.list_versions(asset.id)] == [1, 2, 3] assert [event.operation for event in reloaded.list_audit_events(target="asset:asset-readme")] == [ "asset.create", "asset.metadata.add", "asset.lifecycle.transition", ] def test_sqlite_registry_enforces_representation_asset_reference(tmp_path: Path) -> None: repo = SQLiteAssetRegistryRepository(tmp_path / "registry.sqlite") representation = AssetRepresentation.from_content( "missing-asset", RepresentationKind.NORMALIZED, "text/plain", "normalized", ) with pytest.raises(ValidationError, match="unknown asset"): repo.save_representation(representation) def operation_context() -> OperationContext: actor = Actor.create( ActorType.HUMAN, actor_id="user-test", display_name="Test User", groups=["engineering"], ) return OperationContext.create(actor, correlation_id="corr-test") class DenyLifecyclePolicy: def authorize( self, context: OperationContext, action: str, resource: str, *, resource_metadata: dict[str, str] | None = None, ) -> PolicyDecision: if action == "asset.lifecycle.transition": return PolicyDecision.fail_closed( context.actor.id, action, resource, reason="lifecycle transitions require review", context={"resource_metadata": resource_metadata or {}}, ) return PolicyDecision.allow(context.actor.id, action, resource)