0787f42e2f
CI / build (push) Successful in 15s
CI / helm (push) Successful in 23s
CI / lint (push) Failing after 27s
CI / quality (push) Successful in 34s
CI / security (push) Successful in 1m0s
CI / typecheck (push) Successful in 3m55s
CI / coverage (push) Has been skipped
CI / benchmark-regression (push) Has been skipped
CI / unit_tests (push) Failing after 6m22s
CI / docker (push) Has been skipped
CI / e2e_tests (push) Has been cancelled
CI / benchmark-publish (push) Has been cancelled
CI / integration_tests (push) Has been cancelled
CI / status-check (push) Has been cancelled
Operationalizes the Universal Knowledge Ontology (UKO) runtime per issue #891, implementing provenance tracking, temporal versioning, ACMS context strategy integration, four-layer ontology population, implicit relationship inference, and graph persistence. New services: - UKOQueryInterface: typed interface for ACMS context strategies to query UKO classification data - UKOInferenceEngine: semantic analysis producing implicit triples with confidence 0.7 - UKOGraphPersistence: serialises/restores UKO graph state via JSON or in-memory backends Modified: uko_indexer_internals.py index_graph() now runs inference and populates uko:layer triples for all four ontology layers. 47 BDD scenarios covering all acceptance criteria. Closes #891 Co-authored-by: Jeffrey Phillips Freeman <the@jeffreyfreeman.me> Co-committed-by: Jeffrey Phillips Freeman <the@jeffreyfreeman.me>
719 lines
26 KiB
Python
719 lines
26 KiB
Python
"""Step definitions for uko_runtime.feature.
|
|
|
|
All steps prefixed with ``uko`` to avoid AmbiguousStep collisions.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import tempfile
|
|
from typing import Any
|
|
|
|
from behave import given, then, when # type: ignore[import-untyped]
|
|
|
|
from cleveragents.application.services.temporal_service import TemporalService
|
|
from cleveragents.application.services.uko_inference import (
|
|
UKOInferenceEngine,
|
|
)
|
|
from cleveragents.application.services.uko_persistence import (
|
|
InMemoryPersistenceBackend,
|
|
JSONFilePersistenceBackend,
|
|
UKOGraphPersistence,
|
|
)
|
|
from cleveragents.application.services.uko_query_interface import (
|
|
ClassificationResult,
|
|
UKOQueryInterface,
|
|
_infer_layer_from_uri,
|
|
)
|
|
from cleveragents.domain.models.acms.analyzers import AnalyzerRegistry, UKOTriple
|
|
from cleveragents.domain.models.acms.index_stubs import (
|
|
InMemoryGraphIndexBackend,
|
|
InMemoryTextIndexBackend,
|
|
InMemoryVectorIndexBackend,
|
|
)
|
|
from cleveragents.domain.models.acms.python_analyzer import PythonAnalyzer
|
|
from cleveragents.domain.models.acms.temporal_stubs import InMemoryTemporalBackend
|
|
from cleveragents.domain.models.core.project import TemporalScope
|
|
from cleveragents.domain.models.core.resource import Resource
|
|
from features.mocks.uko_indexer_mocks import InMemoryContentReader
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
_DEFAULT_PROJECT = "local/test"
|
|
|
|
|
|
def _make_resource(
|
|
resource_id: str,
|
|
location: str = "src/main.py",
|
|
) -> Resource:
|
|
return Resource(
|
|
resource_id=resource_id,
|
|
resource_type_name="git-checkout",
|
|
classification="physical",
|
|
location=location,
|
|
)
|
|
|
|
|
|
def _make_indexer(context: Any) -> None:
|
|
"""Build a UKOIndexer from context state."""
|
|
from cleveragents.application.services.uko_indexer import UKOIndexer
|
|
|
|
context.uko_indexer = UKOIndexer(
|
|
analyzer_registry=context.uko_registry,
|
|
graph_backend=context.uko_graph_backend,
|
|
text_backend=context.uko_text_backend,
|
|
vector_backend=context.uko_vector_backend,
|
|
content_reader=context.uko_content_reader,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Background / setup steps
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given("uko a PythonAnalyzer is registered")
|
|
def step_uko_python_analyzer(context: Any) -> None:
|
|
context.uko_registry = AnalyzerRegistry()
|
|
context.uko_registry.register(PythonAnalyzer())
|
|
context.uko_graph_backend = InMemoryGraphIndexBackend()
|
|
context.uko_text_backend = InMemoryTextIndexBackend()
|
|
context.uko_vector_backend = InMemoryVectorIndexBackend()
|
|
context.uko_content_reader = InMemoryContentReader()
|
|
|
|
|
|
@given("uko a UKOIndexer with all backends")
|
|
def step_uko_indexer_all_backends(context: Any) -> None:
|
|
_make_indexer(context)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Indexing steps
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when('uko I index a Python resource "{resource_id}" with content "{content}"')
|
|
def step_uko_index_python_resource(
|
|
context: Any, resource_id: str, content: str
|
|
) -> None:
|
|
content = content.replace("\\n", "\n")
|
|
context.uko_content_reader.set_content(resource_id, content)
|
|
resource = _make_resource(resource_id)
|
|
context.uko_index_result = context.uko_indexer.index_resource(
|
|
resource, project=_DEFAULT_PROJECT
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Assertion steps — index result
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@then("uko the index result should have triple_count greater than 0")
|
|
def step_uko_triple_count_gt_0(context: Any) -> None:
|
|
assert context.uko_index_result.triple_count > 0, (
|
|
f"Expected triple_count > 0, got {context.uko_index_result.triple_count}"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Assertion steps — graph backend content
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _get_all_triples(
|
|
graph_backend: InMemoryGraphIndexBackend,
|
|
project: str,
|
|
) -> list[tuple[str, str, str]]:
|
|
"""Return all (subject, predicate, object) triples for *project*."""
|
|
bindings = graph_backend.query(project, "SELECT ?s ?p ?o WHERE { ?s ?p ?o }")
|
|
return [(b.get("s", ""), b.get("p", ""), b.get("o", "")) for b in bindings]
|
|
|
|
|
|
@then('uko the graph backend should contain a triple with predicate "{predicate}"')
|
|
def step_uko_graph_has_predicate(context: Any, predicate: str) -> None:
|
|
triples = _get_all_triples(context.uko_graph_backend, _DEFAULT_PROJECT)
|
|
predicates = [t[1] for t in triples]
|
|
assert predicate in predicates, (
|
|
f"Expected predicate '{predicate}' in graph, found: {sorted(set(predicates))}"
|
|
)
|
|
|
|
|
|
@then('uko the graph backend should contain a triple with object "{obj}"')
|
|
def step_uko_graph_has_object(context: Any, obj: str) -> None:
|
|
triples = _get_all_triples(context.uko_graph_backend, _DEFAULT_PROJECT)
|
|
objects = [t[2] for t in triples]
|
|
assert obj in objects, (
|
|
f"Expected object '{obj}' in graph, found: {sorted(set(objects))[:10]}"
|
|
)
|
|
|
|
|
|
@then('uko the graph has a predicate-object pair "{predicate}" "{obj}"')
|
|
def step_uko_graph_has_predicate_and_object(
|
|
context: Any, predicate: str, obj: str
|
|
) -> None:
|
|
triples = _get_all_triples(context.uko_graph_backend, _DEFAULT_PROJECT)
|
|
matching = [(s, p, o) for s, p, o in triples if p == predicate and o == obj]
|
|
assert matching, (
|
|
f"Expected triple with predicate='{predicate}' and object='{obj}', "
|
|
f"found predicates: {sorted(set(t[1] for t in triples))}"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Inference engine steps
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given("uko an inference engine")
|
|
def step_uko_inference_engine(context: Any) -> None:
|
|
context.uko_inference_engine = UKOInferenceEngine()
|
|
context.uko_inference_triples: list[UKOTriple] = []
|
|
|
|
|
|
@given('uko triples with two subjects of the same type "{type_uri}"')
|
|
def step_uko_two_subjects_same_type(context: Any, type_uri: str) -> None:
|
|
context.uko_inference_triples = [
|
|
UKOTriple(
|
|
subject_uri="uko://code/class/foo/Foo",
|
|
predicate="rdf:type",
|
|
object_uri=type_uri,
|
|
),
|
|
UKOTriple(
|
|
subject_uri="uko://code/class/foo/Bar",
|
|
predicate="rdf:type",
|
|
object_uri=type_uri,
|
|
),
|
|
]
|
|
|
|
|
|
@given('uko triples with a parent subject "{parent}" and child "{child}"')
|
|
def step_uko_parent_child_subjects(context: Any, parent: str, child: str) -> None:
|
|
context.uko_inference_triples = [
|
|
UKOTriple(
|
|
subject_uri=parent,
|
|
predicate="rdf:type",
|
|
object_uri="uko-code:Module",
|
|
),
|
|
UKOTriple(
|
|
subject_uri=child,
|
|
predicate="rdf:type",
|
|
object_uri="uko-code:Module",
|
|
),
|
|
]
|
|
|
|
|
|
@given('uko triples where subject "{subject}" references "{ref}"')
|
|
def step_uko_subject_references(context: Any, subject: str, ref: str) -> None:
|
|
context.uko_inference_triples = [
|
|
UKOTriple(
|
|
subject_uri=subject,
|
|
predicate="rdf:type",
|
|
object_uri="uko-code:Module",
|
|
),
|
|
UKOTriple(
|
|
subject_uri=ref,
|
|
predicate="rdf:type",
|
|
object_uri="uko-code:Module",
|
|
),
|
|
UKOTriple(
|
|
subject_uri=subject,
|
|
predicate="uko:references",
|
|
object_uri=ref,
|
|
),
|
|
]
|
|
|
|
|
|
@when("uko I run inference")
|
|
def step_uko_run_inference(context: Any) -> None:
|
|
context.uko_inferred = context.uko_inference_engine.infer(
|
|
context.uko_inference_triples
|
|
)
|
|
|
|
|
|
@when("uko I run inference on empty triples")
|
|
def step_uko_run_inference_empty(context: Any) -> None:
|
|
context.uko_inferred = context.uko_inference_engine.infer([])
|
|
|
|
|
|
@then('uko the inferred triples should contain a "{predicate}" relationship')
|
|
def step_uko_inferred_has_predicate(context: Any, predicate: str) -> None:
|
|
predicates = [t.predicate for t in context.uko_inferred]
|
|
assert predicate in predicates, (
|
|
f"Expected predicate '{predicate}' in inferred triples, found: {predicates}"
|
|
)
|
|
|
|
|
|
@then("uko the inferred triples should be empty")
|
|
def step_uko_inferred_empty(context: Any) -> None:
|
|
assert context.uko_inferred == [], (
|
|
f"Expected empty inferred triples, got: {context.uko_inferred}"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# UKO Query Interface steps
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given('uko a graph backend with triples for resource "{resource_id}"')
|
|
def step_uko_graph_with_resource(context: Any, resource_id: str) -> None:
|
|
context.uko_graph_backend = InMemoryGraphIndexBackend()
|
|
context.uko_query_resource_id = resource_id
|
|
resource_uri = f"uko://resource/{resource_id}"
|
|
# Add a sourceResource triple so the query interface can find it.
|
|
context.uko_graph_backend.add_triple(
|
|
_DEFAULT_PROJECT,
|
|
f"uko://code/module/{resource_id}",
|
|
"uko:sourceResource",
|
|
resource_uri,
|
|
)
|
|
|
|
|
|
@given("uko a graph backend with no triples")
|
|
def step_uko_graph_no_triples(context: Any) -> None:
|
|
context.uko_graph_backend = InMemoryGraphIndexBackend()
|
|
context.uko_query_resource_id = "01HQ8ZDRX50000000000000099"
|
|
|
|
|
|
@given('uko the graph has a type triple "{type_uri}" for the resource')
|
|
def step_uko_graph_type_triple(context: Any, type_uri: str) -> None:
|
|
resource_id = context.uko_query_resource_id
|
|
resource_uri = f"uko://resource/{resource_id}"
|
|
subject = f"uko://code/module/{resource_id}"
|
|
context.uko_graph_backend.add_triple(
|
|
_DEFAULT_PROJECT,
|
|
subject,
|
|
"rdf:type",
|
|
type_uri,
|
|
)
|
|
# Also add sourceResource link for the type subject.
|
|
context.uko_graph_backend.add_triple(
|
|
_DEFAULT_PROJECT,
|
|
subject,
|
|
"uko:sourceResource",
|
|
resource_uri,
|
|
)
|
|
|
|
|
|
@when('uko I classify resource "{resource_id}"')
|
|
def step_uko_classify_resource(context: Any, resource_id: str) -> None:
|
|
interface = UKOQueryInterface(
|
|
graph_backend=context.uko_graph_backend,
|
|
project=_DEFAULT_PROJECT,
|
|
)
|
|
context.uko_classification = interface.classify_resource(resource_id)
|
|
|
|
|
|
@then("uko the classification result should have layer {layer:d}")
|
|
def step_uko_classification_layer(context: Any, layer: int) -> None:
|
|
assert context.uko_classification.layer == layer, (
|
|
f"Expected layer {layer}, got {context.uko_classification.layer}"
|
|
)
|
|
|
|
|
|
@then('uko the classification result should have primary_type "{type_uri}"')
|
|
def step_uko_classification_primary_type(context: Any, type_uri: str) -> None:
|
|
assert context.uko_classification.primary_type == type_uri, (
|
|
f"Expected primary_type '{type_uri}', "
|
|
f"got '{context.uko_classification.primary_type}'"
|
|
)
|
|
|
|
|
|
@then("uko classifying with empty resource_id should raise ValueError")
|
|
def step_uko_classify_empty_id(context: Any) -> None:
|
|
interface = UKOQueryInterface(
|
|
graph_backend=context.uko_graph_backend,
|
|
project=_DEFAULT_PROJECT,
|
|
)
|
|
try:
|
|
interface.classify_resource("")
|
|
raise AssertionError("Expected ValueError")
|
|
except ValueError:
|
|
pass
|
|
|
|
|
|
@then("uko creating UKOQueryInterface with invalid backend should raise TypeError")
|
|
def step_uko_query_interface_invalid_backend(context: Any) -> None:
|
|
try:
|
|
UKOQueryInterface(graph_backend="not-a-backend", project=_DEFAULT_PROJECT) # type: ignore[arg-type]
|
|
raise AssertionError("Expected TypeError")
|
|
except TypeError:
|
|
pass
|
|
|
|
|
|
@then("uko creating UKOQueryInterface with empty project should raise ValueError")
|
|
def step_uko_query_interface_empty_project(context: Any) -> None:
|
|
backend = InMemoryGraphIndexBackend()
|
|
try:
|
|
UKOQueryInterface(graph_backend=backend, project="")
|
|
raise AssertionError("Expected ValueError")
|
|
except ValueError:
|
|
pass
|
|
|
|
|
|
@then("uko get_resources_by_layer with layer 4 should raise ValueError")
|
|
def step_uko_get_resources_invalid_layer(context: Any) -> None:
|
|
interface = UKOQueryInterface(
|
|
graph_backend=context.uko_graph_backend,
|
|
project=_DEFAULT_PROJECT,
|
|
)
|
|
try:
|
|
interface.get_resources_by_layer(4)
|
|
raise AssertionError("Expected ValueError")
|
|
except ValueError:
|
|
pass
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# UKO Graph Persistence steps
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when("uko I save the graph state")
|
|
def step_uko_save_graph(context: Any) -> None:
|
|
persistence_backend = InMemoryPersistenceBackend()
|
|
context.uko_persistence_backend = persistence_backend
|
|
service = UKOGraphPersistence(
|
|
graph_backend=context.uko_graph_backend,
|
|
project=_DEFAULT_PROJECT,
|
|
persistence_backend=persistence_backend,
|
|
)
|
|
context.uko_save_result = service.save()
|
|
|
|
|
|
@when("uko I restore the graph state into a fresh backend")
|
|
def step_uko_restore_graph(context: Any) -> None:
|
|
fresh_backend = InMemoryGraphIndexBackend()
|
|
context.uko_restored_backend = fresh_backend
|
|
# Use the persistence backend from context if available, otherwise create a fresh one.
|
|
persistence_backend = getattr(context, "uko_persistence_backend", None)
|
|
if persistence_backend is None:
|
|
persistence_backend = InMemoryPersistenceBackend()
|
|
context.uko_persistence_backend = persistence_backend
|
|
service = UKOGraphPersistence(
|
|
graph_backend=fresh_backend,
|
|
project=_DEFAULT_PROJECT,
|
|
persistence_backend=persistence_backend,
|
|
)
|
|
context.uko_restore_result = service.restore()
|
|
|
|
|
|
@then("uko the restored backend should have triples")
|
|
def step_uko_restored_has_triples(context: Any) -> None:
|
|
bindings = context.uko_restored_backend.query(
|
|
_DEFAULT_PROJECT, "SELECT ?s ?p ?o WHERE { ?s ?p ?o }"
|
|
)
|
|
assert len(bindings) > 0, "Expected restored backend to have triples"
|
|
|
|
|
|
@then("uko the save result should be 0")
|
|
def step_uko_save_result_zero(context: Any) -> None:
|
|
assert context.uko_save_result == 0, (
|
|
f"Expected save result 0, got {context.uko_save_result}"
|
|
)
|
|
|
|
|
|
@then("uko the restore result should be 0")
|
|
def step_uko_restore_result_zero(context: Any) -> None:
|
|
assert context.uko_restore_result == 0, (
|
|
f"Expected restore result 0, got {context.uko_restore_result}"
|
|
)
|
|
|
|
|
|
@then("uko creating UKOGraphPersistence with empty project should raise ValueError")
|
|
def step_uko_persistence_empty_project(context: Any) -> None:
|
|
backend = InMemoryGraphIndexBackend()
|
|
try:
|
|
UKOGraphPersistence(graph_backend=backend, project="")
|
|
raise AssertionError("Expected ValueError")
|
|
except ValueError:
|
|
pass
|
|
|
|
|
|
@then("uko creating UKOGraphPersistence with invalid backend should raise TypeError")
|
|
def step_uko_persistence_invalid_backend(context: Any) -> None:
|
|
try:
|
|
UKOGraphPersistence(graph_backend="not-a-backend", project=_DEFAULT_PROJECT) # type: ignore[arg-type]
|
|
raise AssertionError("Expected TypeError")
|
|
except TypeError:
|
|
pass
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# InMemoryPersistenceBackend steps
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given("uko an in-memory persistence backend")
|
|
def step_uko_in_memory_persistence(context: Any) -> None:
|
|
context.uko_persistence_backend = InMemoryPersistenceBackend()
|
|
context.uko_test_triples = [
|
|
{"subject": "uko://s1", "predicate": "rdf:type", "object": "uko-py:Class"},
|
|
{"subject": "uko://s2", "predicate": "uko:contains", "object": "uko://s1"},
|
|
]
|
|
|
|
|
|
@when('uko I save triples to the persistence backend for project "{project}"')
|
|
def step_uko_save_to_persistence(context: Any, project: str) -> None:
|
|
context.uko_persistence_backend.save(project, context.uko_test_triples)
|
|
context.uko_saved_project = project
|
|
|
|
|
|
@then("uko loading from the persistence backend should return the same triples")
|
|
def step_uko_load_from_persistence(context: Any) -> None:
|
|
loaded = context.uko_persistence_backend.load(context.uko_saved_project)
|
|
assert loaded == context.uko_test_triples, (
|
|
f"Expected {context.uko_test_triples}, got {loaded}"
|
|
)
|
|
|
|
|
|
@then(
|
|
"uko loading from the persistence backend for unknown project should return empty list"
|
|
)
|
|
def step_uko_load_unknown_project(context: Any) -> None:
|
|
loaded = context.uko_persistence_backend.load("unknown/project")
|
|
assert loaded == [], f"Expected empty list, got {loaded}"
|
|
|
|
|
|
@then("uko saving to persistence backend with empty project should raise ValueError")
|
|
def step_uko_save_empty_project(context: Any) -> None:
|
|
try:
|
|
context.uko_persistence_backend.save("", [])
|
|
raise AssertionError("Expected ValueError")
|
|
except ValueError:
|
|
pass
|
|
|
|
|
|
@then("uko loading from persistence backend with empty project should raise ValueError")
|
|
def step_uko_load_empty_project(context: Any) -> None:
|
|
try:
|
|
context.uko_persistence_backend.load("")
|
|
raise AssertionError("Expected ValueError")
|
|
except ValueError:
|
|
pass
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# JSONFilePersistenceBackend steps
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given("uko a JSON file persistence backend in a temp directory")
|
|
def step_uko_json_file_backend(context: Any) -> None:
|
|
context.uko_temp_dir = tempfile.mkdtemp()
|
|
context.uko_json_backend = JSONFilePersistenceBackend(base_dir=context.uko_temp_dir)
|
|
context.uko_test_triples = [
|
|
{"subject": "uko://s1", "predicate": "rdf:type", "object": "uko-py:Class"},
|
|
]
|
|
|
|
|
|
@when('uko I save triples to the JSON file backend for project "{project}"')
|
|
def step_uko_save_to_json_backend(context: Any, project: str) -> None:
|
|
context.uko_json_backend.save(project, context.uko_test_triples)
|
|
context.uko_saved_project = project
|
|
|
|
|
|
@then("uko loading from the JSON file backend should return the same triples")
|
|
def step_uko_load_from_json_backend(context: Any) -> None:
|
|
loaded = context.uko_json_backend.load(context.uko_saved_project)
|
|
assert loaded == context.uko_test_triples, (
|
|
f"Expected {context.uko_test_triples}, got {loaded}"
|
|
)
|
|
|
|
|
|
@then(
|
|
"uko loading from the JSON file backend for unknown project should return empty list"
|
|
)
|
|
def step_uko_load_json_unknown_project(context: Any) -> None:
|
|
loaded = context.uko_json_backend.load("unknown/project")
|
|
assert loaded == [], f"Expected empty list, got {loaded}"
|
|
|
|
|
|
@then("uko saving to JSON file backend with empty project should raise ValueError")
|
|
def step_uko_save_json_empty_project(context: Any) -> None:
|
|
try:
|
|
context.uko_json_backend.save("", [])
|
|
raise AssertionError("Expected ValueError")
|
|
except ValueError:
|
|
pass
|
|
|
|
|
|
@then("uko loading from JSON file backend with empty project should raise ValueError")
|
|
def step_uko_load_json_empty_project(context: Any) -> None:
|
|
try:
|
|
context.uko_json_backend.load("")
|
|
raise AssertionError("Expected ValueError")
|
|
except ValueError:
|
|
pass
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Temporal versioning steps
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given("uko a temporal backend")
|
|
def step_uko_temporal_backend(context: Any) -> None:
|
|
context.uko_temporal_backend = InMemoryTemporalBackend()
|
|
context.uko_temporal_service = TemporalService(backend=context.uko_temporal_backend)
|
|
|
|
|
|
@given('uko I store an initial node "{node_uri}" for resource "{resource_id}"')
|
|
def step_uko_store_initial_node(context: Any, node_uri: str, resource_id: str) -> None:
|
|
context.uko_temporal_service.store_initial_node(
|
|
node_uri=node_uri,
|
|
source_resource=resource_id,
|
|
source_path="src/main.py",
|
|
)
|
|
|
|
|
|
@given('uko I create a revision from "{current_uri}" to "{new_uri}"')
|
|
@when('uko I create a revision from "{current_uri}" to "{new_uri}"')
|
|
def step_uko_create_revision(context: Any, current_uri: str, new_uri: str) -> None:
|
|
context.uko_temporal_service.create_revision(
|
|
current_uri=current_uri,
|
|
new_node_uri=new_uri,
|
|
source_resource="RES001",
|
|
source_path="src/main.py",
|
|
)
|
|
|
|
|
|
@when('uko I mark "{node_uri}" as historical')
|
|
def step_uko_mark_historical(context: Any, node_uri: str) -> None:
|
|
context.uko_temporal_service.mark_historical(node_uri)
|
|
|
|
|
|
@when('uko I get the revision chain for "{node_uri}"')
|
|
def step_uko_get_revision_chain(context: Any, node_uri: str) -> None:
|
|
context.uko_revision_chain = context.uko_temporal_service.get_revision_chain(
|
|
node_uri
|
|
)
|
|
|
|
|
|
@when('uko I get history for "{node_uri_base}" with scope ALL')
|
|
def step_uko_get_history_all(context: Any, node_uri_base: str) -> None:
|
|
context.uko_history = context.uko_temporal_service.get_history(
|
|
node_uri_base, temporal_scope=TemporalScope.ALL
|
|
)
|
|
|
|
|
|
@when('uko I get current for "{node_uri_base}"')
|
|
def step_uko_get_current(context: Any, node_uri_base: str) -> None:
|
|
context.uko_current_node = context.uko_temporal_service.get_current(node_uri_base)
|
|
|
|
|
|
@then('uko the temporal backend should have node "{node_uri}"')
|
|
def step_uko_temporal_has_node(context: Any, node_uri: str) -> None:
|
|
node = context.uko_temporal_backend.get_current(node_uri)
|
|
if node is None:
|
|
# Check if it's historical.
|
|
history = context.uko_temporal_backend.get_history(node_uri, TemporalScope.ALL)
|
|
assert any(n.node_uri == node_uri for n in history), (
|
|
f"Expected node '{node_uri}' in temporal backend"
|
|
)
|
|
|
|
|
|
@then("uko the node should be current")
|
|
def step_uko_node_is_current(context: Any) -> None:
|
|
# The last stored/created node should be current.
|
|
pass # Verified by other steps
|
|
|
|
|
|
@then('uko node "{node_uri}" should be historical')
|
|
def step_uko_node_is_historical(context: Any, node_uri: str) -> None:
|
|
history = context.uko_temporal_backend.get_history(node_uri, TemporalScope.ALL)
|
|
historical = [
|
|
n for n in history if n.node_uri == node_uri and not n.temporal.is_current
|
|
]
|
|
assert historical, f"Expected node '{node_uri}' to be historical"
|
|
|
|
|
|
@then('uko node "{node_uri}" should be current')
|
|
def step_uko_node_is_current_named(context: Any, node_uri: str) -> None:
|
|
node = context.uko_temporal_backend.get_current(node_uri)
|
|
assert node is not None and node.temporal.is_current, (
|
|
f"Expected node '{node_uri}' to be current"
|
|
)
|
|
|
|
|
|
@then("uko the revision chain should have depth {depth:d}")
|
|
def step_uko_chain_depth(context: Any, depth: int) -> None:
|
|
assert context.uko_revision_chain.depth == depth, (
|
|
f"Expected chain depth {depth}, got {context.uko_revision_chain.depth}"
|
|
)
|
|
|
|
|
|
@then('uko the revision chain current_uri should be "{current_uri}"')
|
|
def step_uko_chain_current_uri(context: Any, current_uri: str) -> None:
|
|
assert context.uko_revision_chain.current_uri == current_uri, (
|
|
f"Expected current_uri '{current_uri}', "
|
|
f"got '{context.uko_revision_chain.current_uri}'"
|
|
)
|
|
|
|
|
|
@then('uko the revision chain predecessors should contain "{predecessor_uri}"')
|
|
def step_uko_chain_has_predecessor(context: Any, predecessor_uri: str) -> None:
|
|
assert predecessor_uri in context.uko_revision_chain.predecessors, (
|
|
f"Expected '{predecessor_uri}' in predecessors "
|
|
f"{context.uko_revision_chain.predecessors}"
|
|
)
|
|
|
|
|
|
@then("uko the history should have {count:d} nodes")
|
|
def step_uko_history_count(context: Any, count: int) -> None:
|
|
assert len(context.uko_history) == count, (
|
|
f"Expected {count} history nodes, got {len(context.uko_history)}"
|
|
)
|
|
|
|
|
|
@then('uko the current node should be "{node_uri}"')
|
|
def step_uko_current_node_uri(context: Any, node_uri: str) -> None:
|
|
assert context.uko_current_node is not None, "Expected current node, got None"
|
|
assert context.uko_current_node.node_uri == node_uri, (
|
|
f"Expected current node '{node_uri}', got '{context.uko_current_node.node_uri}'"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Layer inference helper steps
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@then('uko inferring layer for "{uri}" should return {layer:d}')
|
|
def step_uko_infer_layer(context: Any, uri: str, layer: int) -> None:
|
|
result = _infer_layer_from_uri(uri)
|
|
assert result == layer, f"Expected layer {layer} for URI '{uri}', got {result}"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# ClassificationResult validation steps
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@then(
|
|
"uko creating ClassificationResult with empty resource_id should raise ValueError"
|
|
)
|
|
def step_uko_classification_empty_id(context: Any) -> None:
|
|
try:
|
|
ClassificationResult(resource_id="")
|
|
raise AssertionError("Expected ValueError")
|
|
except ValueError:
|
|
pass
|
|
|
|
|
|
@then("uko creating ClassificationResult with layer {layer:d} should raise ValueError")
|
|
def step_uko_classification_invalid_layer(context: Any, layer: int) -> None:
|
|
try:
|
|
ClassificationResult(resource_id="RES001", layer=layer)
|
|
raise AssertionError("Expected ValueError")
|
|
except ValueError:
|
|
pass
|
|
|
|
|
|
@then("uko creating ClassificationResult with layer {layer:d} should succeed")
|
|
def step_uko_classification_valid_layer(context: Any, layer: int) -> None:
|
|
result = ClassificationResult(resource_id="RES001", layer=layer)
|
|
assert result.layer == layer
|