Feature: Temporal Data Model (Revision-Aware RDF) with 3 Storage Tiers
  As a developer
  I want temporal metadata on UKO nodes with revision chains and tier-aware queries
  So that the ACMS can track knowledge graph evolution and enable temporal-archaeology

  # ---- TemporalMetadata model ----
  Scenario: Create a valid TemporalMetadata with defaults
    Given a TemporalMetadata with valid_from now
    Then the temporal metadata is_current should be true
    And the temporal metadata valid_until should be None
    And the temporal metadata is_revision_of should be None
    And the temporal metadata valid_from should have UTC timezone

  Scenario: Create a TemporalMetadata with all fields
    Given a TemporalMetadata with valid_from now and valid_until tomorrow and is_current false and is_revision_of "uko-py:class/Foo_v1"
    Then the temporal metadata is_current should be false
    And the temporal metadata valid_until should not be None
    And the temporal metadata is_revision_of should be "uko-py:class/Foo_v1"

  Scenario: TemporalMetadata is frozen
    Given a TemporalMetadata with valid_from now
    Then modifying the temporal metadata is_current should raise an error

  Scenario: TemporalMetadata rejects empty is_revision_of
    Then creating a TemporalMetadata with empty is_revision_of should raise ValueError
  Scenario: TemporalMetadata rejects whitespace-only is_revision_of
    Then creating a TemporalMetadata with whitespace is_revision_of should raise ValueError
  Scenario: TemporalNode rejects zero-width Unicode node_uri
    Then creating a TemporalNode with zero-width Unicode node_uri should raise ValueError
  Scenario: TemporalMetadata rejects zero-width Unicode is_revision_of
    Then creating a TemporalMetadata with zero-width Unicode is_revision_of should raise ValueError

  Scenario: TemporalMetadata adds UTC to naive valid_from
    Given a TemporalMetadata with naive valid_from
    Then the temporal metadata valid_from should have UTC timezone

  Scenario: TemporalMetadata adds UTC to naive valid_until
    Given a TemporalMetadata with naive valid_until
    Then the temporal metadata valid_until should have UTC timezone

  Scenario: TemporalMetadata converts non-UTC aware datetime to UTC
    Given a TemporalMetadata with non-UTC timezone
    Then the temporal metadata valid_from should have UTC timezone

  Scenario: TemporalMetadata coerces ISO-8601 string datetime to UTC
    Given a TemporalMetadata with valid_from as ISO-8601 string
    Then the temporal metadata valid_from should have UTC timezone

  Scenario: TemporalMetadata rejects valid_until before valid_from
    Then creating a TemporalMetadata with valid_until before valid_from should raise ValueError
  Scenario: TemporalMetadata rejects is_current with valid_until set
    Then creating a TemporalMetadata with is_current true and valid_until set should raise ValueError
  Scenario: TemporalMetadata rejects is_current false without valid_until
    Then creating a TemporalMetadata with is_current false and no valid_until should raise ValueError

  # ---- TemporalNode model ----
  Scenario: Create a valid TemporalNode
    Given a TemporalNode with uri "uko-py:class/Auth_v1" and resource "RES001" and path "src/auth.py" without range
    Then the temporal node uri should be "uko-py:class/Auth_v1"
    And the temporal node source_resource should be "RES001"
    And the temporal node source_path should be "src/auth.py"
    And the temporal node source_range should be None
    And the temporal node should have temporal metadata

  Scenario: TemporalNode with source_range
    Given a TemporalNode with uri "uko-py:class/Auth_v1" and resource "RES001" and path "src/auth.py" and range "15:1-87:0"
    Then the temporal node source_range should be "15:1-87:0"

  Scenario: TemporalNode is frozen
    Given a TemporalNode with uri "uko-py:class/Auth_v1" and resource "RES001" and path "src/auth.py" without range
    Then modifying the temporal node uri should raise an error

  Scenario: TemporalNode rejects empty node_uri
    Then creating a TemporalNode with empty node_uri should raise ValueError
  Scenario: TemporalNode rejects whitespace-only node_uri
    Then creating a TemporalNode with whitespace node_uri should raise ValueError
  Scenario: TemporalNode rejects empty source_resource
    Then creating a TemporalNode with empty source_resource should raise ValueError
  Scenario: TemporalNode rejects empty source_path
    Then creating a TemporalNode with empty source_path should raise ValueError
  Scenario: TemporalNode rejects empty source_range
    Then creating a TemporalNode with empty source_range should raise ValueError
  Scenario: TemporalNode rejects whitespace-only source_range
    Then creating a TemporalNode with whitespace source_range should raise ValueError

  # ---- RevisionChain model ----
  Scenario: Create a RevisionChain with no predecessors
    Given a RevisionChain with only current_uri "uko-py:class/Auth_v1"
    Then the revision chain depth should be 1
    And the revision chain predecessors should be empty
    And the revision chain all_uris should contain "uko-py:class/Auth_v1"

  Scenario: Create a RevisionChain with predecessors
    Given a RevisionChain with current_uri "uko-py:class/Auth_v3" and predecessors "uko-py:class/Auth_v1,uko-py:class/Auth_v2"
    Then the revision chain depth should be 3
    And the revision chain predecessors should have 2 entries
    And the revision chain all_uris should have 3 entries

  Scenario: RevisionChain is frozen
    Given a RevisionChain with only current_uri "uko-py:class/Auth_v1"
    Then modifying the revision chain current_uri should raise an error

  Scenario: RevisionChain rejects empty current_uri
    Then creating a RevisionChain with empty current_uri should raise ValueError
  Scenario: RevisionChain rejects whitespace-only current_uri
    Then creating a RevisionChain with whitespace current_uri should raise ValueError
  Scenario: RevisionChain rejects empty predecessor URI
    Then creating a RevisionChain with empty predecessor should raise ValueError
  Scenario: RevisionChain rejects current_uri in predecessors
    Then creating a RevisionChain with current_uri in predecessors should raise ValueError
  Scenario: RevisionChain rejects duplicate predecessors
    Then creating a RevisionChain with duplicate predecessors should raise ValueError

  # ---- TierRetentionConfig model ----
  Scenario: TierRetentionConfig defaults match spec
    Given a TierRetentionConfig with defaults
    Then the warm retention hours should be 24
    And the cold retention days should be 90

  Scenario: TierRetentionConfig is frozen
    Given a TierRetentionConfig with defaults
    Then modifying the retention warm hours should raise an error

  Scenario: TierRetentionConfig rejects zero warm hours
    Then creating a TierRetentionConfig with warm_retention_hours 0 should raise ValueError
  Scenario: TierRetentionConfig rejects zero cold days
    Then creating a TierRetentionConfig with cold_retention_days 0 should raise ValueError

  # ---- TierQueryResult model ----
  Scenario: Create a TierQueryResult with no nodes
    Given a TierQueryResult for HOT tier with CURRENT scope
    Then the tier query result nodes should be empty
    And the tier query result tier should be HOT
    And the tier query result temporal_scope should be CURRENT

  Scenario: TierQueryResult is frozen
    Given a TierQueryResult for HOT tier with CURRENT scope
    Then modifying the tier query result tier should raise an error

  # ---- TemporalBackend protocol ----
  Scenario: InMemoryTemporalBackend satisfies TemporalBackend protocol
    Given an InMemoryTemporalBackend instance
    Then it should satisfy the TemporalBackend protocol

  # ---- InMemoryTemporalBackend: store and get ----
  Scenario: Store a node and get current
    Given an InMemoryTemporalBackend instance
    And a stored temporal node "uko-py:class/Auth_v1" for resource "RES001" at "src/auth.py"
    When I get the current node for base "uko-py:class/Auth"
    Then the current node uri should be "uko-py:class/Auth_v1"

  Scenario: Get current returns None when no match
    Given an InMemoryTemporalBackend instance
    When I get the current node for base "uko-py:class/NonExistent"
    Then the current node should be None

  Scenario: Get current does not match unrelated URI prefix
    Given an InMemoryTemporalBackend instance
    And a stored temporal node "uko-py:class/AuthManager_v1" for resource "RES001" at "src/auth.py"
    When I get the current node for base "uko-py:class/Auth"
    Then the current node should be None

  # ---- InMemoryTemporalBackend: create revision ----
  Scenario: Create a revision marks old node historical
    Given an InMemoryTemporalBackend instance
    And a stored temporal node "uko-py:class/Auth_v1" for resource "RES001" at "src/auth.py"
    When I create a revision from "uko-py:class/Auth_v1" to "uko-py:class/Auth_v2"
    Then the old node "uko-py:class/Auth_v1" should not be current
    And the old node "uko-py:class/Auth_v1" should have valid_until set
    And the new node "uko-py:class/Auth_v2" should be current
    And the new node "uko-py:class/Auth_v2" should have is_revision_of "uko-py:class/Auth_v1"

  Scenario: Create revision from nonexistent URI raises KeyError
    Given an InMemoryTemporalBackend instance
    Then creating a revision from nonexistent "uko-py:class/Missing" should raise KeyError
  Scenario: Create revision with blank URI raises ValueError
    Given an InMemoryTemporalBackend instance
    Then creating a revision from blank URI should raise ValueError

  Scenario: Create revision with same URI as current raises ValueError
    Given an InMemoryTemporalBackend instance
    And a stored temporal node "uko-py:class/Auth_v1" for resource "RES001" at "src/auth.py"
    Then creating a revision from "uko-py:class/Auth_v1" to itself should raise ValueError

  Scenario: Store node rejects duplicate URI
    Given an InMemoryTemporalBackend instance
    And a stored temporal node "uko-py:class/Auth_v1" for resource "RES001" at "src/auth.py"
    Then storing a duplicate node "uko-py:class/Auth_v1" should raise ValueError

  Scenario: Create revision rejects URI that already exists
    Given an InMemoryTemporalBackend instance
    And a stored temporal node "uko-py:class/Auth_v1" for resource "RES001" at "src/auth.py"
    And a stored temporal node "uko-py:class/Auth_v2" for resource "RES001" at "src/auth.py"
    Then creating a revision from "uko-py:class/Auth_v1" to existing "uko-py:class/Auth_v2" should raise ValueError

  Scenario: Create revision rejects wrong is_revision_of on new node
    Given an InMemoryTemporalBackend instance
    And a stored temporal node "uko-py:class/Auth_v1" for resource "RES001" at "src/auth.py"
    Then creating a revision from "uko-py:class/Auth_v1" with wrong is_revision_of should raise ValueError
  Scenario: Create revision rejects is_current false on new node
    Given an InMemoryTemporalBackend instance
    And a stored temporal node "uko-py:class/Auth_v1" for resource "RES001" at "src/auth.py"
    Then creating a revision from "uko-py:class/Auth_v1" with is_current false should raise ValueError

  # ---- InMemoryTemporalBackend: get history ----
  Scenario: Get history with ALL scope returns all versions
    Given an InMemoryTemporalBackend instance
    And a stored temporal node "uko-py:class/Auth_v1" for resource "RES001" at "src/auth.py"
    And I create a revision from "uko-py:class/Auth_v1" to "uko-py:class/Auth_v2"
    When I get the history for base "uko-py:class/Auth" with scope ALL
    Then the history should have 2 nodes
    And the history should be ordered newest first

  Scenario: Get history with CURRENT scope returns only current
    Given an InMemoryTemporalBackend instance
    And a stored temporal node "uko-py:class/Auth_v1" for resource "RES001" at "src/auth.py"
    And I create a revision from "uko-py:class/Auth_v1" to "uko-py:class/Auth_v2"
    When I get the history for base "uko-py:class/Auth" with scope CURRENT
    Then the history should have 1 nodes

  Scenario: Get history with blank base URI raises ValueError
    Given an InMemoryTemporalBackend instance
    Then getting history for blank base URI should raise ValueError

  # ---- InMemoryTemporalBackend: revision chain ----
  Scenario: Get revision chain for single node
    Given an InMemoryTemporalBackend instance
    And a stored temporal node "uko-py:class/Auth_v1" for resource "RES001" at "src/auth.py"
    When I get the revision chain for "uko-py:class/Auth_v1"
    Then the revision chain should have depth 1
    And the revision chain current should be "uko-py:class/Auth_v1"

  Scenario: Get revision chain after multiple revisions
    Given an InMemoryTemporalBackend instance
    And a stored temporal node "uko-py:class/Auth_v1" for resource "RES001" at "src/auth.py"
    And I create a revision from "uko-py:class/Auth_v1" to "uko-py:class/Auth_v2"
    And I create a revision from "uko-py:class/Auth_v2" to "uko-py:class/Auth_v3"
    When I get the revision chain for "uko-py:class/Auth_v3"
    Then the revision chain should have depth 3
    And the revision chain current should be "uko-py:class/Auth_v3"
    And the revision chain predecessors should include "uko-py:class/Auth_v1"
    And the revision chain predecessors should include "uko-py:class/Auth_v2"

  Scenario: Get revision chain from middle node includes full chain
    Given an InMemoryTemporalBackend instance
    And a stored temporal node "uko-py:class/Auth_v1" for resource "RES001" at "src/auth.py"
    And I create a revision from "uko-py:class/Auth_v1" to "uko-py:class/Auth_v2"
    And I create a revision from "uko-py:class/Auth_v2" to "uko-py:class/Auth_v3"
    When I get the revision chain for "uko-py:class/Auth_v1"
    Then the revision chain should have depth 3

  Scenario: Get revision chain for nonexistent URI raises KeyError
    Given an InMemoryTemporalBackend instance
    Then getting revision chain for nonexistent URI should raise KeyError

  Scenario: Fully-historical revision chain raises ValueError
    Given an InMemoryTemporalBackend instance
    And a stored temporal node with all nodes historical
    Then getting revision chain for fully-historical node should raise ValueError

  # ---- InMemoryTemporalBackend: query by tier ----
  Scenario: Hot tier query returns only current nodes
    Given an InMemoryTemporalBackend instance
    And a stored temporal node "uko-py:class/Auth_v1" for resource "RES001" at "src/auth.py"
    And I create a revision from "uko-py:class/Auth_v1" to "uko-py:class/Auth_v2"
    When I query the HOT tier
    Then the tier query should return 1 node
    And the tier query node should be current

  Scenario: Cold tier query returns all temporal versions
    Given an InMemoryTemporalBackend instance
    And a stored temporal node "uko-py:class/Auth_v1" for resource "RES001" at "src/auth.py"
    And I create a revision from "uko-py:class/Auth_v1" to "uko-py:class/Auth_v2"
    When I query the COLD tier
    Then the tier query should return 2 nodes

  # ---- InMemoryTemporalBackend: mark historical ----
  Scenario: Mark a node as historical
    Given an InMemoryTemporalBackend instance
    And a stored temporal node "uko-py:class/Auth_v1" for resource "RES001" at "src/auth.py"
    When I mark "uko-py:class/Auth_v1" as historical
    Then the marked node should not be current
    And the marked node should have valid_until set

  Scenario: Mark historical with blank URI raises ValueError
    Given an InMemoryTemporalBackend instance
    Then marking blank URI as historical should raise ValueError
  Scenario: Mark historical with nonexistent URI raises KeyError
    Given an InMemoryTemporalBackend instance
    Then marking nonexistent URI as historical should raise KeyError
  Scenario: Mark historical on already-historical node raises ValueError
    Given an InMemoryTemporalBackend instance
    And a stored temporal node "uko-py:class/Auth_v1" for resource "RES001" at "src/auth.py"
    And I mark "uko-py:class/Auth_v1" as historical
    Then marking already-historical "uko-py:class/Auth_v1" should raise ValueError

  Scenario: Create revision on already-historical node raises ValueError
    Given an InMemoryTemporalBackend instance
    And a stored temporal node "uko-py:class/Auth_v1" for resource "RES001" at "src/auth.py"
    And I create a revision from "uko-py:class/Auth_v1" to "uko-py:class/Auth_v2"
    Then creating a revision from historical "uko-py:class/Auth_v1" to "uko-py:class/Auth_v3" should raise ValueError

  Scenario: Get revision chain identifies current head explicitly
    Given an InMemoryTemporalBackend instance
    And a stored temporal node "uko-py:class/Auth_v1" for resource "RES001" at "src/auth.py"
    And I create a revision from "uko-py:class/Auth_v1" to "uko-py:class/Auth_v2"
    When I get the revision chain for "uko-py:class/Auth_v1"
    Then the revision chain current_uri should be "uko-py:class/Auth_v2"
    And the revision chain predecessors should contain "uko-py:class/Auth_v1"

  Scenario: Get revision chain excludes sibling branch nodes
    Given an InMemoryTemporalBackend instance
    And a stored temporal node with branching revision chain
    When I get the revision chain for "uko-py:class/Branch_B"
    Then the revision chain should not contain "uko-py:class/Branch_C"
    And the revision chain current_uri should be "uko-py:class/Branch_B"

  # ---- InMemoryTemporalBackend: diff-coverage scenarios ----
  Scenario: Get history with RECENT scope returns current and recently expired nodes
    Given an InMemoryTemporalBackend instance
    And a stored temporal node "uko-py:class/Auth_v1" for resource "RES001" at "src/auth.py"
    And I create a revision from "uko-py:class/Auth_v1" to "uko-py:class/Auth_v2"
    When I get the history for base "uko-py:class/Auth" with scope RECENT
    Then the history should have 2 nodes

  Scenario: Revision chain with cycle guard stops traversal
    Given an InMemoryTemporalBackend instance
    And a stored temporal node with cyclic revision chain
    When I get the revision chain for "uko-py:class/Cycle_B"
    Then the revision chain should have depth 2

  Scenario: Revision chain with missing predecessor stops traversal
    Given an InMemoryTemporalBackend instance
    And a stored temporal node with dangling predecessor
    When I get the revision chain for "uko-py:class/Dangling_B"
    Then the revision chain should have depth 1
  Scenario: Warm tier query returns current and recently expired nodes
    Given an InMemoryTemporalBackend instance
    And a stored temporal node "uko-py:class/Auth_v1" for resource "RES001" at "src/auth.py"
    And I create a revision from "uko-py:class/Auth_v1" to "uko-py:class/Auth_v2"
    When I query the WARM tier
    Then the tier query should return 2 nodes

  Scenario: Query by tier with RECENT scope filters within tier pool
    Given an InMemoryTemporalBackend instance
    And a stored temporal node "uko-py:class/Auth_v1" for resource "RES001" at "src/auth.py"
    And I create a revision from "uko-py:class/Auth_v1" to "uko-py:class/Auth_v2"
    When I query the HOT tier with scope RECENT
    Then the tier query should return 1 node
  # ---- TemporalService ----
  Scenario: TemporalService store initial node
    Given a TemporalService with InMemoryTemporalBackend
    When I store an initial node "uko-py:class/Foo_v1" for resource "RES001" at "src/foo.py"
    Then the stored node should be current
    And the stored node should have no predecessor

  Scenario: TemporalService create revision
    Given a TemporalService with InMemoryTemporalBackend
    And I store an initial node "uko-py:class/Foo_v1" for resource "RES001" at "src/foo.py"
    When I create a service revision from "uko-py:class/Foo_v1" to "uko-py:class/Foo_v2" for resource "RES001" at "src/foo.py"
    Then the service revision node should be current
    And the service revision node should have is_revision_of "uko-py:class/Foo_v1"

  Scenario: TemporalService get current
    Given a TemporalService with InMemoryTemporalBackend
    And I store an initial node "uko-py:class/Foo_v1" for resource "RES001" at "src/foo.py"
    When I get the service current for "uko-py:class/Foo"
    Then the service current node uri should be "uko-py:class/Foo_v1"

  Scenario: TemporalService query by tier uses default scope
    Given a TemporalService with InMemoryTemporalBackend
    And I store an initial node "uko-py:class/Foo_v1" for resource "RES001" at "src/foo.py"
    When I query HOT tier via the service
    Then the service tier query scope should be CURRENT

  Scenario: TemporalService query by tier allows scope override
    Given a TemporalService with InMemoryTemporalBackend
    And I store an initial node "uko-py:class/Foo_v1" for resource "RES001" at "src/foo.py"
    When I query HOT tier via the service with scope ALL
    Then the service tier query scope should be ALL

  Scenario: TemporalService mark historical
    Given a TemporalService with InMemoryTemporalBackend
    And I store an initial node "uko-py:class/Foo_v1" for resource "RES001" at "src/foo.py"
    When I mark "uko-py:class/Foo_v1" as historical via the service
    Then the service marked node should not be current

  Scenario: TemporalService rejects None backend
    Then creating a TemporalService with None backend should raise TypeError

  Scenario: TemporalService rejects blank node_uri
    Given a TemporalService with InMemoryTemporalBackend
    Then storing an initial node with blank URI via the service should raise ValueError
  Scenario: TemporalService rejects blank source_resource
    Given a TemporalService with InMemoryTemporalBackend
    Then storing an initial node with blank source_resource via the service should raise ValueError

  Scenario: TemporalService get history returns matching versions
    Given a TemporalService with InMemoryTemporalBackend
    And I store an initial node "uko-py:class/Foo_v1" for resource "RES001" at "src/foo.py"
    When I create a service revision from "uko-py:class/Foo_v1" to "uko-py:class/Foo_v2" for resource "RES001" at "src/foo.py"
    And I get the service history for "uko-py:class/Foo" with scope ALL
    Then the service history should have 2 nodes

  Scenario: TemporalService get history with CURRENT scope returns only current
    Given a TemporalService with InMemoryTemporalBackend
    And I store an initial node "uko-py:class/Foo_v1" for resource "RES001" at "src/foo.py"
    When I create a service revision from "uko-py:class/Foo_v1" to "uko-py:class/Foo_v2" for resource "RES001" at "src/foo.py"
    And I get the service history for "uko-py:class/Foo" with scope CURRENT
    Then the service history should have 1 nodes

  Scenario: TemporalService get history rejects blank base URI
    Given a TemporalService with InMemoryTemporalBackend
    Then getting service history for blank base URI should raise ValueError
  Scenario: TemporalService get revision chain returns full chain
    Given a TemporalService with InMemoryTemporalBackend
    And I store an initial node "uko-py:class/Foo_v1" for resource "RES001" at "src/foo.py"
    When I create a service revision from "uko-py:class/Foo_v1" to "uko-py:class/Foo_v2" for resource "RES001" at "src/foo.py"
    And I get the service revision chain for "uko-py:class/Foo_v2"
    Then the service revision chain depth should be 2
    And the service revision chain current should be "uko-py:class/Foo_v2"

  Scenario: TemporalService get revision chain rejects blank URI
    Given a TemporalService with InMemoryTemporalBackend
    Then getting service revision chain for blank URI should raise ValueError
  Scenario: TemporalService query COLD with CURRENT scope returns only current
    Given a TemporalService with InMemoryTemporalBackend
    And I store an initial node "uko-py:class/Foo_v1" for resource "RES001" at "src/foo.py"
    When I create a service revision from "uko-py:class/Foo_v1" to "uko-py:class/Foo_v2" for resource "RES001" at "src/foo.py"
    And I query COLD tier via the service with scope CURRENT
    Then the service tier query should return 1 node
    And the service tier query node should be current

  Scenario: TemporalService default retention matches spec
    Given a TemporalService with InMemoryTemporalBackend
    Then the service retention warm hours should be 24
    And the service retention cold days should be 90

  Scenario: TemporalService WARM tier uses RECENT default scope
    Given a TemporalService with InMemoryTemporalBackend
    And I store an initial node "uko-py:class/Foo_v1" for resource "RES001" at "src/foo.py"
    When I query WARM tier via the service
    Then the service tier query scope should be RECENT

  Scenario: TemporalService COLD tier uses ALL default scope
    Given a TemporalService with InMemoryTemporalBackend
    And I store an initial node "uko-py:class/Foo_v1" for resource "RES001" at "src/foo.py"
    When I query COLD tier via the service
    Then the service tier query scope should be ALL

  Scenario: TemporalService create revision with same URI raises ValueError
    Given a TemporalService with InMemoryTemporalBackend
    And I store an initial node "uko-py:class/Foo_v1" for resource "RES001" at "src/foo.py"
    Then creating a service revision from "uko-py:class/Foo_v1" to itself should raise ValueError
  Scenario: TemporalService mark historical on already-historical raises ValueError
    Given a TemporalService with InMemoryTemporalBackend
    And I store an initial node "uko-py:class/Foo_v1" for resource "RES001" at "src/foo.py"
    When I mark "uko-py:class/Foo_v1" as historical via the service
    Then marking "uko-py:class/Foo_v1" as historical again via the service should raise ValueError

  # ---- BackendSet temporal typing ----
  Scenario: BackendSet accepts TemporalBackend
    Given an InMemoryTemporalBackend instance
    When I create a BackendSet with the temporal backend
    Then the BackendSet temporal field should not be None

  Scenario: StrategyConfig extra deep-freezes nested dicts
    Given a StrategyConfig with nested extra dict
    Then mutating the nested extra dict should raise TypeError

  Scenario: StrategyConfig extra deep-freezes nested lists to tuples
    Given a StrategyConfig with nested list in extra
    Then the nested list should be frozen as a tuple

  # ---- Structural assertions (Phase 3.1) ----
  Scenario: TemporalMetadata has all spec-required fields
    Then TemporalMetadata should have field "valid_from"
    And TemporalMetadata should have field "valid_until"
    And TemporalMetadata should have field "is_current"
    And TemporalMetadata should have field "is_revision_of"
  Scenario: TemporalNode has all spec-required fields
    Then TemporalNode should have field "node_uri"
    And TemporalNode should have field "source_resource"
    And TemporalNode should have field "source_path"
    And TemporalNode should have field "source_range"
    And TemporalNode should have field "temporal"
  Scenario: RevisionChain has all spec-required fields
    Then RevisionChain should have field "current_uri"
    And RevisionChain should have field "predecessors"
  Scenario: TierRetentionConfig has all spec-required fields
    Then TierRetentionConfig should have field "warm_retention_hours"
    And TierRetentionConfig should have field "cold_retention_days"
  Scenario: TierQueryResult has all spec-required fields
    Then TierQueryResult should have field "nodes"
    And TierQueryResult should have field "tier"
    And TierQueryResult should have field "temporal_scope"
