Files
cleveragents-core/features/uko_runtime.feature
T
freemo 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
feat(acms): operationalize UKO runtime with provenance and temporal versioning
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
2026-04-02 11:28:50 +00:00

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