d194ee0d95
Add layer 2 (paradigm/OO) triple emission to PythonAnalyzer and add the missing Scenario for layer 2 indexing in uko_runtime.feature. - PythonAnalyzer._extract_class() now emits a rdf:type uko-oo:Class triple (layer 2) in addition to rdf:type uko-py:Class (layer 3), ensuring UKOIndexer emits layer 2 triples when indexing Python OOP constructs (classes). - Added Scenario: Indexing a Python file populates layer 2 (paradigm) to features/uko_runtime.feature, completing the four-layer guarantee verification (layers 0, 1, 2, 3) at the runtime/indexing level. ISSUES CLOSED: #9351
273 lines
14 KiB
Gherkin
273 lines
14 KiB
Gherkin
Feature: UKO Runtime — Provenance Tracking, Temporal Versioning, and ACMS Integration
|
|
As a context pipeline consumer
|
|
I need the UKO runtime to produce typed triples with provenance, support temporal
|
|
versioning, and integrate with ACMS context strategies
|
|
So that the knowledge graph is semantically rich and queryable across time
|
|
|
|
# =================================================================
|
|
# UKO Indexer produces provenance-annotated triples
|
|
# =================================================================
|
|
|
|
Scenario: UKO indexer produces typed triples with provenance metadata
|
|
Given uko a PythonAnalyzer is registered
|
|
And uko a UKOIndexer with all backends
|
|
When uko I index a Python resource "01HQ8ZDRX50000000000000001" with content "class Foo:\n pass"
|
|
Then uko the index result should have triple_count greater than 0
|
|
And uko the graph backend should contain a triple with predicate "uko:sourceResource"
|
|
And uko the graph backend should contain a triple with predicate "uko:validFrom"
|
|
And uko the graph backend should contain a triple with predicate "uko:isCurrent"
|
|
|
|
Scenario: Provenance metadata has correct source_resource value
|
|
Given uko a PythonAnalyzer is registered
|
|
And uko a UKOIndexer with all backends
|
|
When uko I index a Python resource "01HQ8ZDRX50000000000000002" with content "def bar(): pass"
|
|
Then uko the graph backend should contain a triple with object "uko://resource/01HQ8ZDRX50000000000000002"
|
|
|
|
Scenario: Provenance metadata marks triples as current
|
|
Given uko a PythonAnalyzer is registered
|
|
And uko a UKOIndexer with all backends
|
|
When uko I index a Python resource "01HQ8ZDRX50000000000000003" with content "x = 1"
|
|
Then uko the graph has a predicate-object pair "uko:isCurrent" "true"
|
|
|
|
# =================================================================
|
|
# Four ontology layers are populated during indexing
|
|
# =================================================================
|
|
|
|
Scenario: Indexing a Python file populates layer 0 (universal)
|
|
Given uko a PythonAnalyzer is registered
|
|
And uko a UKOIndexer with all backends
|
|
When uko I index a Python resource "01HQ8ZDRX50000000000000004" with content "class Foo:\n pass"
|
|
Then uko the graph has a predicate-object pair "uko:layer" "0"
|
|
|
|
Scenario: Indexing a Python file populates layer 1 (domain)
|
|
Given uko a PythonAnalyzer is registered
|
|
And uko a UKOIndexer with all backends
|
|
When uko I index a Python resource "01HQ8ZDRX50000000000000005" with content "class Foo:\n pass"
|
|
Then uko the graph has a predicate-object pair "uko:layer" "1"
|
|
|
|
Scenario: Indexing a Python file populates layer 3 (technology)
|
|
Given uko a PythonAnalyzer is registered
|
|
And uko a UKOIndexer with all backends
|
|
When uko I index a Python resource "01HQ8ZDRX50000000000000006" with content "class Foo:\n pass"
|
|
Then uko the graph has a predicate-object pair "uko:layer" "3"
|
|
|
|
Scenario: Indexing a Python file populates layer 2 (paradigm)
|
|
Given uko a PythonAnalyzer is registered
|
|
And uko a UKOIndexer with all backends
|
|
When uko I index a Python resource "01HQ8ZDRX50000000000000012" with content "class Foo:\n pass"
|
|
Then uko the graph has a predicate-object pair "uko:layer" "2"
|
|
|
|
# =================================================================
|
|
# Implicit relationship inference
|
|
# =================================================================
|
|
|
|
Scenario: Inference engine produces sibling relationships for same-type subjects
|
|
Given uko an inference engine
|
|
And uko triples with two subjects of the same type "uko-py:Class"
|
|
When uko I run inference
|
|
Then uko the inferred triples should contain a "uko:implicitSiblingOf" relationship
|
|
|
|
Scenario: Inference engine produces containment relationships
|
|
Given uko an inference engine
|
|
And uko triples with a parent subject "uko://code/module/foo" and child "uko://code/module/foo/bar"
|
|
When uko I run inference
|
|
Then uko the inferred triples should contain a "uko:implicitContains" relationship
|
|
|
|
Scenario: Inference engine produces dependency relationships
|
|
Given uko an inference engine
|
|
And uko triples where subject "uko://code/module/a" references "uko://code/module/b"
|
|
When uko I run inference
|
|
Then uko the inferred triples should contain a "uko:implicitDependsOn" relationship
|
|
|
|
Scenario: Inference engine returns empty list for empty input
|
|
Given uko an inference engine
|
|
When uko I run inference on empty triples
|
|
Then uko the inferred triples should be empty
|
|
|
|
Scenario: Indexer stores implicit relationship triples in graph backend
|
|
Given uko a PythonAnalyzer is registered
|
|
And uko a UKOIndexer with all backends
|
|
When uko I index a Python resource "01HQ8ZDRX50000000000000007" with content "class Foo:\n pass\nclass Bar:\n pass"
|
|
Then uko the graph backend should contain a triple with predicate "uko:implicitSiblingOf"
|
|
|
|
# =================================================================
|
|
# UKO Query Interface — ACMS context strategy integration
|
|
# =================================================================
|
|
|
|
Scenario: UKO query interface classifies a resource by layer
|
|
Given uko a graph backend with triples for resource "01HQ8ZDRX50000000000000008"
|
|
And uko the graph has a type triple "uko-py:Class" for the resource
|
|
When uko I classify resource "01HQ8ZDRX50000000000000008"
|
|
Then uko the classification result should have layer 3
|
|
|
|
Scenario: UKO query interface returns layer 0 for unclassified resource
|
|
Given uko a graph backend with no triples
|
|
When uko I classify resource "01HQ8ZDRX50000000000000009"
|
|
Then uko the classification result should have layer 0
|
|
|
|
Scenario: UKO query interface returns primary type
|
|
Given uko a graph backend with triples for resource "01HQ8ZDRX50000000000000010"
|
|
And uko the graph has a type triple "uko-py:Class" for the resource
|
|
When uko I classify resource "01HQ8ZDRX50000000000000010"
|
|
Then uko the classification result should have primary_type "uko-py:Class"
|
|
|
|
Scenario: UKO query interface rejects empty resource_id
|
|
Given uko a graph backend with no triples
|
|
Then uko classifying with empty resource_id should raise ValueError
|
|
|
|
Scenario: UKO query interface rejects invalid graph_backend type
|
|
Then uko creating UKOQueryInterface with invalid backend should raise TypeError
|
|
|
|
Scenario: UKO query interface rejects empty project
|
|
Then uko creating UKOQueryInterface with empty project should raise ValueError
|
|
|
|
Scenario: UKO query interface get_resources_by_layer rejects invalid layer
|
|
Given uko a graph backend with no triples
|
|
Then uko get_resources_by_layer with layer 4 should raise ValueError
|
|
|
|
# =================================================================
|
|
# UKO Graph Persistence
|
|
# =================================================================
|
|
|
|
Scenario: UKO graph persistence saves and restores triples
|
|
Given uko a graph backend with triples for resource "01HQ8ZDRX50000000000000011"
|
|
And uko the graph has a type triple "uko-py:Class" for the resource
|
|
When uko I save the graph state
|
|
And uko I restore the graph state into a fresh backend
|
|
Then uko the restored backend should have triples
|
|
|
|
Scenario: UKO graph persistence returns 0 for empty graph
|
|
Given uko a graph backend with no triples
|
|
When uko I save the graph state
|
|
Then uko the save result should be 0
|
|
|
|
Scenario: UKO graph persistence restore returns 0 when no data persisted
|
|
Given uko a graph backend with no triples
|
|
When uko I restore the graph state into a fresh backend
|
|
Then uko the restore result should be 0
|
|
|
|
Scenario: UKO graph persistence rejects empty project
|
|
Then uko creating UKOGraphPersistence with empty project should raise ValueError
|
|
|
|
Scenario: UKO graph persistence rejects invalid graph_backend type
|
|
Then uko creating UKOGraphPersistence with invalid backend should raise TypeError
|
|
|
|
Scenario: InMemoryPersistenceBackend save and load round-trip
|
|
Given uko an in-memory persistence backend
|
|
When uko I save triples to the persistence backend for project "local/test"
|
|
Then uko loading from the persistence backend should return the same triples
|
|
|
|
Scenario: InMemoryPersistenceBackend returns empty list for unknown project
|
|
Given uko an in-memory persistence backend
|
|
Then uko loading from the persistence backend for unknown project should return empty list
|
|
|
|
Scenario: InMemoryPersistenceBackend rejects empty project on save
|
|
Given uko an in-memory persistence backend
|
|
Then uko saving to persistence backend with empty project should raise ValueError
|
|
|
|
Scenario: InMemoryPersistenceBackend rejects empty project on load
|
|
Given uko an in-memory persistence backend
|
|
Then uko loading from persistence backend with empty project should raise ValueError
|
|
|
|
Scenario: JSONFilePersistenceBackend save and load round-trip
|
|
Given uko a JSON file persistence backend in a temp directory
|
|
When uko I save triples to the JSON file backend for project "local/test"
|
|
Then uko loading from the JSON file backend should return the same triples
|
|
|
|
Scenario: JSONFilePersistenceBackend returns empty list for unknown project
|
|
Given uko a JSON file persistence backend in a temp directory
|
|
Then uko loading from the JSON file backend for unknown project should return empty list
|
|
|
|
Scenario: JSONFilePersistenceBackend rejects empty project on save
|
|
Given uko a JSON file persistence backend in a temp directory
|
|
Then uko saving to JSON file backend with empty project should raise ValueError
|
|
|
|
Scenario: JSONFilePersistenceBackend rejects empty project on load
|
|
Given uko a JSON file persistence backend in a temp directory
|
|
Then uko loading from JSON file backend with empty project should raise ValueError
|
|
|
|
# =================================================================
|
|
# Temporal versioning — revision chain
|
|
# =================================================================
|
|
|
|
Scenario: Temporal service stores initial node
|
|
Given uko a temporal backend
|
|
And uko I store an initial node "uko-py:class/Foo_v1" for resource "RES001"
|
|
Then uko the temporal backend should have node "uko-py:class/Foo_v1"
|
|
And uko the node should be current
|
|
|
|
Scenario: Temporal service creates a revision
|
|
Given uko a temporal backend
|
|
And uko I store an initial node "uko-py:class/Foo_v1" for resource "RES001"
|
|
And uko I create a revision from "uko-py:class/Foo_v1" to "uko-py:class/Foo_v1_r2"
|
|
Then uko the temporal backend should have node "uko-py:class/Foo_v1_r2"
|
|
And uko node "uko-py:class/Foo_v1" should be historical
|
|
And uko node "uko-py:class/Foo_v1_r2" should be current
|
|
|
|
Scenario: Temporal service get_revision_chain returns full chain
|
|
Given uko a temporal backend
|
|
And uko I store an initial node "uko-py:class/Foo_v1" for resource "RES001"
|
|
And uko I create a revision from "uko-py:class/Foo_v1" to "uko-py:class/Foo_v1_r2"
|
|
When uko I get the revision chain for "uko-py:class/Foo_v1_r2"
|
|
Then uko the revision chain should have depth 2
|
|
And uko the revision chain current_uri should be "uko-py:class/Foo_v1_r2"
|
|
And uko the revision chain predecessors should contain "uko-py:class/Foo_v1"
|
|
|
|
Scenario: Temporal service get_history returns all versions
|
|
Given uko a temporal backend
|
|
And uko I store an initial node "uko-py:class/Foo_v1" for resource "RES001"
|
|
And uko I create a revision from "uko-py:class/Foo_v1" to "uko-py:class/Foo_v1_r2"
|
|
When uko I get history for "uko-py:class/Foo_v1" with scope ALL
|
|
Then uko the history should have 2 nodes
|
|
|
|
Scenario: Temporal service get_current returns the current node
|
|
Given uko a temporal backend
|
|
And uko I store an initial node "uko-py:class/Foo_v1" for resource "RES001"
|
|
And uko I create a revision from "uko-py:class/Foo_v1" to "uko-py:class/Foo_v1_r2"
|
|
When uko I get current for "uko-py:class/Foo_v1"
|
|
Then uko the current node should be "uko-py:class/Foo_v1_r2"
|
|
|
|
Scenario: Temporal service mark_historical marks a node as historical
|
|
Given uko a temporal backend
|
|
And uko I store an initial node "uko-py:class/Foo_v1" for resource "RES001"
|
|
When uko I mark "uko-py:class/Foo_v1" as historical
|
|
Then uko node "uko-py:class/Foo_v1" should be historical
|
|
|
|
# =================================================================
|
|
# Layer inference helper
|
|
# =================================================================
|
|
|
|
Scenario: Layer inference returns 0 for universal UKO URI
|
|
Then uko inferring layer for "uko:Resource" should return 0
|
|
|
|
Scenario: Layer inference returns 1 for domain UKO URI
|
|
Then uko inferring layer for "uko-code:Module" should return 1
|
|
|
|
Scenario: Layer inference returns 2 for paradigm UKO URI
|
|
Then uko inferring layer for "uko-oo:Class" should return 2
|
|
|
|
Scenario: Layer inference returns 3 for technology UKO URI
|
|
Then uko inferring layer for "uko-py:Class" should return 3
|
|
|
|
Scenario: Layer inference returns 3 for full IRI technology URI
|
|
Then uko inferring layer for "https://cleveragents.ai/ontology/uko/py#Class" should return 3
|
|
|
|
Scenario: Layer inference returns 0 for unknown URI
|
|
Then uko inferring layer for "unknown:Foo" should return 0
|
|
|
|
# =================================================================
|
|
# ClassificationResult validation
|
|
# =================================================================
|
|
|
|
Scenario: ClassificationResult rejects empty resource_id
|
|
Then uko creating ClassificationResult with empty resource_id should raise ValueError
|
|
|
|
Scenario: ClassificationResult rejects invalid layer
|
|
Then uko creating ClassificationResult with layer 5 should raise ValueError
|
|
|
|
Scenario: ClassificationResult accepts valid layer 0
|
|
Then uko creating ClassificationResult with layer 0 should succeed
|
|
|
|
Scenario: ClassificationResult accepts valid layer 3
|
|
Then uko creating ClassificationResult with layer 3 should succeed
|