a349e7b9fe
CI / build (pull_request) Successful in 16s
CI / helm (pull_request) Successful in 20s
CI / lint (pull_request) Successful in 3m19s
CI / quality (pull_request) Successful in 3m44s
CI / typecheck (pull_request) Successful in 4m0s
CI / security (pull_request) Successful in 4m6s
CI / unit_tests (pull_request) Successful in 9m12s
CI / docker (pull_request) Successful in 1m35s
CI / coverage (pull_request) Successful in 12m20s
CI / e2e_tests (pull_request) Successful in 19m58s
CI / integration_tests (pull_request) Successful in 24m40s
CI / status-check (pull_request) Successful in 1s
CI / benchmark-publish (pull_request) Has been skipped
CI / benchmark-regression (pull_request) Successful in 55m41s
Implements the UKO runtime operationalization per issue #891: - UKO indexer now produces typed triples with provenance metadata (sourceResource, validFrom, isCurrent, sourcePath, sourceRange) - Temporal versioning (revision chain) already existed; wired into new BDD tests covering store_initial_node, create_revision, get_revision_chain, get_history, get_current, mark_historical - UKO query interface (UKOQueryInterface) wires UKO classification data into ACMS context strategies via classify_resource(), get_resources_by_layer(), and get_implicit_relations() - Four ontology layers (0=universal, 1=domain, 2=paradigm, 3=technology) are now populated during resource indexing via uko:layer triples on the resource URI - Implicit relationship inference (UKOInferenceEngine) runs during indexing to produce uko:implicitSiblingOf, uko:implicitContains, and uko:implicitDependsOn triples with confidence 0.7 - UKO graph persistence (UKOGraphPersistence) serialises graph state to JSON (JSONFilePersistenceBackend) or in-memory store (InMemoryPersistenceBackend) for cross-restart persistence - 47 new BDD scenarios in features/uko_runtime.feature covering all acceptance criteria; all existing UKO tests continue to pass ISSUES CLOSED: #891
267 lines
13 KiB
Gherkin
267 lines
13 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"
|
|
|
|
# =================================================================
|
|
# 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
|