Files
cleveragents-core/robot/helper_temporal_data_model.py
hamza.khyari d5f7f15215
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 14s
CI / build (pull_request) Successful in 14s
CI / quality (pull_request) Successful in 20s
CI / security (pull_request) Successful in 34s
CI / typecheck (pull_request) Successful in 39s
CI / unit_tests (pull_request) Successful in 2m50s
CI / integration_tests (pull_request) Successful in 3m22s
CI / docker (pull_request) Successful in 40s
CI / coverage (pull_request) Successful in 5m49s
CI / benchmark-regression (pull_request) Successful in 35m15s
feat(acms): implement Temporal Data Model (Revision-Aware RDF) with 3 storage tiers
Temporal metadata fields (validFrom, validUntil, isCurrent, isRevisionOf)
on UKO InformationUnit nodes enable revision chain tracking: when code
changes, old nodes are marked historical and new revision nodes are
created with back-links. Three storage tiers (hot/warm/cold) filter
nodes by temporal scope (current/recent/all) with configurable retention
(warm_retention_hours default 24h, cold_retention_days default 90d).

Includes TemporalMetadata, TemporalNode, RevisionChain, TierQueryResult,
TierRetentionConfig frozen domain models, TemporalBackend protocol,
InMemoryTemporalBackend stub, TemporalService with structlog and DI,
BackendSet.temporal typing upgrade from object|None to TemporalBackend|None.
67 Behave scenarios, 8 Robot Framework tests, ASV benchmarks, and
reference documentation.

ISSUES CLOSED: #577
2026-03-11 01:36:06 +00:00

364 lines
11 KiB
Python

"""Robot Framework helper for Temporal Data Model smoke tests.
Provides a CLI-style interface for Robot to invoke temporal model
creation, revision chain operations, tier queries, and service-level
operations. Exit code 0 = success, 1 = failure.
Usage:
python robot/helper_temporal_data_model.py <command>
"""
from __future__ import annotations
import sys
from datetime import UTC, datetime, timedelta
from pathlib import Path
# Ensure the src directory is on the import path.
_SRC = str(Path(__file__).resolve().parents[1] / "src")
if _SRC not in sys.path:
sys.path.insert(0, _SRC)
from cleveragents.application.services.temporal_service import ( # noqa: E402
TemporalService,
)
from cleveragents.domain.models.acms.temporal import ( # noqa: E402
RevisionChain,
TemporalBackend,
TemporalMetadata,
TemporalNode,
TierQueryResult,
TierRetentionConfig,
)
from cleveragents.domain.models.acms.temporal_stubs import ( # noqa: E402
InMemoryTemporalBackend,
)
from cleveragents.domain.models.acms.tiers import ContextTier # noqa: E402
from cleveragents.domain.models.core.project import ( # noqa: E402
TemporalScope,
)
def main() -> int:
"""Entry point called by Robot Framework ``Run Process``."""
if len(sys.argv) < 2:
print("Usage: helper_temporal_data_model.py <command>")
return 1
command: str = sys.argv[1]
if command == "protocol-compliance":
return _test_protocol_compliance()
if command == "revision-chain":
return _test_revision_chain()
if command == "tier-queries":
return _test_tier_queries()
if command == "service-lifecycle":
return _test_service_lifecycle()
if command == "model-creation":
return _test_model_creation()
if command == "validation":
return _test_validation()
if command == "retention-defaults":
return _test_retention_defaults()
if command == "integration-modify-resource":
return _test_integration_modify_resource()
print(f"Unknown command: {command}")
return 1
def _test_protocol_compliance() -> int:
"""Verify InMemoryTemporalBackend satisfies TemporalBackend."""
try:
backend = InMemoryTemporalBackend()
assert isinstance(backend, TemporalBackend)
print("temporal-protocol-ok")
return 0
except Exception as exc:
print(f"FAIL: {exc}")
return 1
def _test_revision_chain() -> int:
"""Test revision chain creation and traversal."""
try:
backend = InMemoryTemporalBackend()
now = datetime.now(tz=UTC)
# Store v1
v1 = TemporalNode(
node_uri="uko-py:class/Auth_v1",
source_resource="RES001",
source_path="src/auth.py",
temporal=TemporalMetadata(
valid_from=now - timedelta(days=5),
is_current=True,
),
)
backend.store_node(v1)
# Create v2
v2 = TemporalNode(
node_uri="uko-py:class/Auth_v2",
source_resource="RES001",
source_path="src/auth.py",
temporal=TemporalMetadata(
valid_from=now - timedelta(days=2),
is_current=True,
is_revision_of="uko-py:class/Auth_v1",
),
)
backend.create_revision("uko-py:class/Auth_v1", v2, now - timedelta(days=2))
# Create v3
v3 = TemporalNode(
node_uri="uko-py:class/Auth_v3",
source_resource="RES001",
source_path="src/auth.py",
temporal=TemporalMetadata(
valid_from=now,
is_current=True,
is_revision_of="uko-py:class/Auth_v2",
),
)
backend.create_revision("uko-py:class/Auth_v2", v3, now)
# Verify chain
chain = backend.get_revision_chain("uko-py:class/Auth_v3")
assert chain.depth == 3
assert chain.current_uri == "uko-py:class/Auth_v3"
assert "uko-py:class/Auth_v1" in chain.predecessors
assert "uko-py:class/Auth_v2" in chain.predecessors
print("temporal-revision-chain-ok")
return 0
except Exception as exc:
print(f"FAIL: {exc}")
return 1
def _test_tier_queries() -> int:
"""Test tier-aware temporal queries."""
try:
backend = InMemoryTemporalBackend()
now = datetime.now(tz=UTC)
retention = TierRetentionConfig()
# Store a current node and a historical node
v1 = TemporalNode(
node_uri="uko-py:class/Foo_v1",
source_resource="RES001",
source_path="src/foo.py",
temporal=TemporalMetadata(
valid_from=now - timedelta(days=5),
is_current=True,
),
)
backend.store_node(v1)
v2 = TemporalNode(
node_uri="uko-py:class/Foo_v2",
source_resource="RES001",
source_path="src/foo.py",
temporal=TemporalMetadata(
valid_from=now,
is_current=True,
is_revision_of="uko-py:class/Foo_v1",
),
)
backend.create_revision("uko-py:class/Foo_v1", v2, now)
# Hot tier: only current
hot = backend.query_by_tier(ContextTier.HOT, TemporalScope.CURRENT, retention)
assert len(hot.nodes) == 1
assert hot.nodes[0].temporal.is_current is True
# Cold tier: all
cold = backend.query_by_tier(ContextTier.COLD, TemporalScope.ALL, retention)
assert len(cold.nodes) == 2
print("temporal-tier-queries-ok")
return 0
except Exception as exc:
print(f"FAIL: {exc}")
return 1
def _test_service_lifecycle() -> int:
"""Test TemporalService lifecycle."""
try:
backend = InMemoryTemporalBackend()
service = TemporalService(backend=backend)
# Store initial
node = service.store_initial_node(
node_uri="uko-py:class/Bar_v1",
source_resource="RES002",
source_path="src/bar.py",
)
assert node.temporal.is_current is True
# Create revision
v2 = service.create_revision(
current_uri="uko-py:class/Bar_v1",
new_node_uri="uko-py:class/Bar_v2",
source_resource="RES002",
source_path="src/bar.py",
)
assert v2.temporal.is_revision_of == "uko-py:class/Bar_v1"
# Get current
current = service.get_current("uko-py:class/Bar")
assert current is not None
assert current.node_uri == "uko-py:class/Bar_v2"
# Get chain
chain = service.get_revision_chain("uko-py:class/Bar_v2")
assert chain.depth == 2
print("temporal-service-lifecycle-ok")
return 0
except Exception as exc:
print(f"FAIL: {exc}")
return 1
def _test_model_creation() -> int:
"""Test domain model creation."""
try:
now = datetime.now(tz=UTC)
meta = TemporalMetadata(valid_from=now)
assert meta.is_current is True
assert meta.valid_until is None
node = TemporalNode(
node_uri="uko-py:class/Test_v1",
source_resource="RES",
source_path="src/test.py",
temporal=meta,
)
assert node.node_uri == "uko-py:class/Test_v1"
chain = RevisionChain(current_uri="uko:test")
assert chain.depth == 1
result = TierQueryResult(
tier=ContextTier.HOT,
temporal_scope=TemporalScope.CURRENT,
)
assert result.nodes == ()
retention = TierRetentionConfig()
assert retention.warm_retention_hours == 24
print("temporal-model-creation-ok")
return 0
except Exception as exc:
print(f"FAIL: {exc}")
return 1
def _test_validation() -> int:
"""Test input validation on models and backend."""
try:
backend = InMemoryTemporalBackend()
# Blank URI should raise ValueError
try:
backend.get_current("")
print("FAIL: expected ValueError")
return 1
except ValueError:
pass
# Nonexistent node should raise KeyError
try:
backend.mark_historical("uko:nonexistent", datetime.now(tz=UTC))
print("FAIL: expected KeyError")
return 1
except KeyError:
pass
print("temporal-validation-ok")
return 0
except Exception as exc:
print(f"FAIL: {exc}")
return 1
def _test_retention_defaults() -> int:
"""Test retention config defaults match spec."""
try:
retention = TierRetentionConfig()
assert retention.warm_retention_hours == 24
assert retention.cold_retention_days == 90
print("temporal-retention-defaults-ok")
return 0
except Exception as exc:
print(f"FAIL: {exc}")
return 1
def _test_integration_modify_resource() -> int:
"""Integration test: modify resource, verify temporal chain.
Acceptance criteria #10: Modify a resource, verify old node is
marked historical, new node is current, revision chain is intact.
"""
try:
backend = InMemoryTemporalBackend()
service = TemporalService(backend=backend)
# Step 1: Initial analysis creates first node
v1 = service.store_initial_node(
node_uri="uko-py:class/AuthManager_v1",
source_resource="RES_AUTH",
source_path="src/auth/manager.py",
source_range="15:1-87:0",
)
assert v1.temporal.is_current is True
assert v1.temporal.is_revision_of is None
# Step 2: Code change triggers re-analysis → new revision
v2 = service.create_revision(
current_uri="uko-py:class/AuthManager_v1",
new_node_uri="uko-py:class/AuthManager_v2",
source_resource="RES_AUTH",
source_path="src/auth/manager.py",
source_range="15:1-95:0",
)
# Step 3: Verify old node is marked historical
old = backend._nodes["uko-py:class/AuthManager_v1"]
assert old.temporal.is_current is False
assert old.temporal.valid_until is not None
# Step 4: Verify new node is current
assert v2.temporal.is_current is True
assert v2.temporal.is_revision_of == "uko-py:class/AuthManager_v1"
# Step 5: Verify revision chain is intact
chain = service.get_revision_chain("uko-py:class/AuthManager_v2")
assert chain.depth == 2
assert chain.current_uri == "uko-py:class/AuthManager_v2"
assert "uko-py:class/AuthManager_v1" in chain.predecessors
# Step 6: Hot tier shows only current
hot = service.query_by_tier(ContextTier.HOT)
assert len(hot.nodes) == 1
assert hot.nodes[0].node_uri == "uko-py:class/AuthManager_v2"
# Step 7: Cold tier shows full history
cold = service.query_by_tier(ContextTier.COLD)
assert len(cold.nodes) == 2
print("temporal-integration-modify-resource-ok")
return 0
except Exception as exc:
print(f"FAIL: {exc}")
return 1
if __name__ == "__main__":
sys.exit(main())