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
