tnfr 4.5.2__py3-none-any.whl → 8.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of tnfr might be problematic. Click here for more details.
- tnfr/__init__.py +334 -50
- tnfr/__init__.pyi +33 -0
- tnfr/_compat.py +10 -0
- tnfr/_generated_version.py +34 -0
- tnfr/_version.py +49 -0
- tnfr/_version.pyi +7 -0
- tnfr/alias.py +214 -37
- tnfr/alias.pyi +108 -0
- tnfr/backends/__init__.py +354 -0
- tnfr/backends/jax_backend.py +173 -0
- tnfr/backends/numpy_backend.py +238 -0
- tnfr/backends/optimized_numpy.py +420 -0
- tnfr/backends/torch_backend.py +408 -0
- tnfr/cache.py +149 -556
- tnfr/cache.pyi +13 -0
- tnfr/cli/__init__.py +51 -16
- tnfr/cli/__init__.pyi +26 -0
- tnfr/cli/arguments.py +344 -32
- tnfr/cli/arguments.pyi +29 -0
- tnfr/cli/execution.py +676 -50
- tnfr/cli/execution.pyi +70 -0
- tnfr/cli/interactive_validator.py +614 -0
- tnfr/cli/utils.py +18 -3
- tnfr/cli/utils.pyi +7 -0
- tnfr/cli/validate.py +236 -0
- tnfr/compat/__init__.py +85 -0
- tnfr/compat/dataclass.py +136 -0
- tnfr/compat/jsonschema_stub.py +61 -0
- tnfr/compat/matplotlib_stub.py +73 -0
- tnfr/compat/numpy_stub.py +155 -0
- tnfr/config/__init__.py +224 -0
- tnfr/config/__init__.pyi +10 -0
- tnfr/{constants_glyphs.py → config/constants.py} +26 -20
- tnfr/config/constants.pyi +12 -0
- tnfr/config/defaults.py +54 -0
- tnfr/{constants/core.py → config/defaults_core.py} +59 -6
- tnfr/config/defaults_init.py +33 -0
- tnfr/config/defaults_metric.py +104 -0
- tnfr/config/feature_flags.py +81 -0
- tnfr/config/feature_flags.pyi +16 -0
- tnfr/config/glyph_constants.py +31 -0
- tnfr/config/init.py +77 -0
- tnfr/config/init.pyi +8 -0
- tnfr/config/operator_names.py +254 -0
- tnfr/config/operator_names.pyi +36 -0
- tnfr/config/physics_derivation.py +354 -0
- tnfr/config/presets.py +83 -0
- tnfr/config/presets.pyi +7 -0
- tnfr/config/security.py +927 -0
- tnfr/config/thresholds.py +114 -0
- tnfr/config/tnfr_config.py +498 -0
- tnfr/constants/__init__.py +51 -133
- tnfr/constants/__init__.pyi +92 -0
- tnfr/constants/aliases.py +33 -0
- tnfr/constants/aliases.pyi +27 -0
- tnfr/constants/init.py +3 -1
- tnfr/constants/init.pyi +12 -0
- tnfr/constants/metric.py +9 -15
- tnfr/constants/metric.pyi +19 -0
- tnfr/core/__init__.py +33 -0
- tnfr/core/container.py +226 -0
- tnfr/core/default_implementations.py +329 -0
- tnfr/core/interfaces.py +279 -0
- tnfr/dynamics/__init__.py +213 -633
- tnfr/dynamics/__init__.pyi +83 -0
- tnfr/dynamics/adaptation.py +267 -0
- tnfr/dynamics/adaptation.pyi +7 -0
- tnfr/dynamics/adaptive_sequences.py +189 -0
- tnfr/dynamics/adaptive_sequences.pyi +14 -0
- tnfr/dynamics/aliases.py +23 -0
- tnfr/dynamics/aliases.pyi +19 -0
- tnfr/dynamics/bifurcation.py +232 -0
- tnfr/dynamics/canonical.py +229 -0
- tnfr/dynamics/canonical.pyi +48 -0
- tnfr/dynamics/coordination.py +385 -0
- tnfr/dynamics/coordination.pyi +25 -0
- tnfr/dynamics/dnfr.py +2699 -398
- tnfr/dynamics/dnfr.pyi +26 -0
- tnfr/dynamics/dynamic_limits.py +225 -0
- tnfr/dynamics/feedback.py +252 -0
- tnfr/dynamics/feedback.pyi +24 -0
- tnfr/dynamics/fused_dnfr.py +454 -0
- tnfr/dynamics/homeostasis.py +157 -0
- tnfr/dynamics/homeostasis.pyi +14 -0
- tnfr/dynamics/integrators.py +496 -102
- tnfr/dynamics/integrators.pyi +36 -0
- tnfr/dynamics/learning.py +310 -0
- tnfr/dynamics/learning.pyi +33 -0
- tnfr/dynamics/metabolism.py +254 -0
- tnfr/dynamics/nbody.py +796 -0
- tnfr/dynamics/nbody_tnfr.py +783 -0
- tnfr/dynamics/propagation.py +326 -0
- tnfr/dynamics/runtime.py +908 -0
- tnfr/dynamics/runtime.pyi +77 -0
- tnfr/dynamics/sampling.py +10 -5
- tnfr/dynamics/sampling.pyi +7 -0
- tnfr/dynamics/selectors.py +711 -0
- tnfr/dynamics/selectors.pyi +85 -0
- tnfr/dynamics/structural_clip.py +207 -0
- tnfr/errors/__init__.py +37 -0
- tnfr/errors/contextual.py +492 -0
- tnfr/execution.py +77 -55
- tnfr/execution.pyi +45 -0
- tnfr/extensions/__init__.py +205 -0
- tnfr/extensions/__init__.pyi +18 -0
- tnfr/extensions/base.py +173 -0
- tnfr/extensions/base.pyi +35 -0
- tnfr/extensions/business/__init__.py +71 -0
- tnfr/extensions/business/__init__.pyi +11 -0
- tnfr/extensions/business/cookbook.py +88 -0
- tnfr/extensions/business/cookbook.pyi +8 -0
- tnfr/extensions/business/health_analyzers.py +202 -0
- tnfr/extensions/business/health_analyzers.pyi +9 -0
- tnfr/extensions/business/patterns.py +183 -0
- tnfr/extensions/business/patterns.pyi +8 -0
- tnfr/extensions/medical/__init__.py +73 -0
- tnfr/extensions/medical/__init__.pyi +11 -0
- tnfr/extensions/medical/cookbook.py +88 -0
- tnfr/extensions/medical/cookbook.pyi +8 -0
- tnfr/extensions/medical/health_analyzers.py +181 -0
- tnfr/extensions/medical/health_analyzers.pyi +9 -0
- tnfr/extensions/medical/patterns.py +163 -0
- tnfr/extensions/medical/patterns.pyi +8 -0
- tnfr/flatten.py +29 -50
- tnfr/flatten.pyi +21 -0
- tnfr/gamma.py +66 -53
- tnfr/gamma.pyi +36 -0
- tnfr/glyph_history.py +144 -57
- tnfr/glyph_history.pyi +35 -0
- tnfr/glyph_runtime.py +19 -0
- tnfr/glyph_runtime.pyi +8 -0
- tnfr/immutable.py +70 -30
- tnfr/immutable.pyi +36 -0
- tnfr/initialization.py +22 -16
- tnfr/initialization.pyi +65 -0
- tnfr/io.py +5 -241
- tnfr/io.pyi +13 -0
- tnfr/locking.pyi +7 -0
- tnfr/mathematics/__init__.py +79 -0
- tnfr/mathematics/backend.py +453 -0
- tnfr/mathematics/backend.pyi +99 -0
- tnfr/mathematics/dynamics.py +408 -0
- tnfr/mathematics/dynamics.pyi +90 -0
- tnfr/mathematics/epi.py +391 -0
- tnfr/mathematics/epi.pyi +65 -0
- tnfr/mathematics/generators.py +242 -0
- tnfr/mathematics/generators.pyi +29 -0
- tnfr/mathematics/metrics.py +119 -0
- tnfr/mathematics/metrics.pyi +16 -0
- tnfr/mathematics/operators.py +239 -0
- tnfr/mathematics/operators.pyi +59 -0
- tnfr/mathematics/operators_factory.py +124 -0
- tnfr/mathematics/operators_factory.pyi +11 -0
- tnfr/mathematics/projection.py +87 -0
- tnfr/mathematics/projection.pyi +33 -0
- tnfr/mathematics/runtime.py +182 -0
- tnfr/mathematics/runtime.pyi +64 -0
- tnfr/mathematics/spaces.py +256 -0
- tnfr/mathematics/spaces.pyi +83 -0
- tnfr/mathematics/transforms.py +305 -0
- tnfr/mathematics/transforms.pyi +62 -0
- tnfr/metrics/__init__.py +47 -9
- tnfr/metrics/__init__.pyi +20 -0
- tnfr/metrics/buffer_cache.py +163 -0
- tnfr/metrics/buffer_cache.pyi +24 -0
- tnfr/metrics/cache_utils.py +214 -0
- tnfr/metrics/coherence.py +1510 -330
- tnfr/metrics/coherence.pyi +129 -0
- tnfr/metrics/common.py +23 -16
- tnfr/metrics/common.pyi +35 -0
- tnfr/metrics/core.py +251 -36
- tnfr/metrics/core.pyi +13 -0
- tnfr/metrics/diagnosis.py +709 -110
- tnfr/metrics/diagnosis.pyi +86 -0
- tnfr/metrics/emergence.py +245 -0
- tnfr/metrics/export.py +60 -18
- tnfr/metrics/export.pyi +7 -0
- tnfr/metrics/glyph_timing.py +233 -43
- tnfr/metrics/glyph_timing.pyi +81 -0
- tnfr/metrics/learning_metrics.py +280 -0
- tnfr/metrics/learning_metrics.pyi +21 -0
- tnfr/metrics/phase_coherence.py +351 -0
- tnfr/metrics/phase_compatibility.py +349 -0
- tnfr/metrics/reporting.py +63 -28
- tnfr/metrics/reporting.pyi +25 -0
- tnfr/metrics/sense_index.py +1126 -43
- tnfr/metrics/sense_index.pyi +9 -0
- tnfr/metrics/trig.py +215 -23
- tnfr/metrics/trig.pyi +13 -0
- tnfr/metrics/trig_cache.py +148 -24
- tnfr/metrics/trig_cache.pyi +10 -0
- tnfr/multiscale/__init__.py +32 -0
- tnfr/multiscale/hierarchical.py +517 -0
- tnfr/node.py +646 -140
- tnfr/node.pyi +139 -0
- tnfr/observers.py +160 -45
- tnfr/observers.pyi +31 -0
- tnfr/ontosim.py +23 -19
- tnfr/ontosim.pyi +28 -0
- tnfr/operators/__init__.py +1358 -106
- tnfr/operators/__init__.pyi +31 -0
- tnfr/operators/algebra.py +277 -0
- tnfr/operators/canonical_patterns.py +420 -0
- tnfr/operators/cascade.py +267 -0
- tnfr/operators/cycle_detection.py +358 -0
- tnfr/operators/definitions.py +4108 -0
- tnfr/operators/definitions.pyi +78 -0
- tnfr/operators/grammar.py +1164 -0
- tnfr/operators/grammar.pyi +140 -0
- tnfr/operators/hamiltonian.py +710 -0
- tnfr/operators/health_analyzer.py +809 -0
- tnfr/operators/jitter.py +107 -38
- tnfr/operators/jitter.pyi +11 -0
- tnfr/operators/lifecycle.py +314 -0
- tnfr/operators/metabolism.py +618 -0
- tnfr/operators/metrics.py +2138 -0
- tnfr/operators/network_analysis/__init__.py +27 -0
- tnfr/operators/network_analysis/source_detection.py +186 -0
- tnfr/operators/nodal_equation.py +395 -0
- tnfr/operators/pattern_detection.py +660 -0
- tnfr/operators/patterns.py +669 -0
- tnfr/operators/postconditions/__init__.py +38 -0
- tnfr/operators/postconditions/mutation.py +236 -0
- tnfr/operators/preconditions/__init__.py +1226 -0
- tnfr/operators/preconditions/coherence.py +305 -0
- tnfr/operators/preconditions/dissonance.py +236 -0
- tnfr/operators/preconditions/emission.py +128 -0
- tnfr/operators/preconditions/mutation.py +580 -0
- tnfr/operators/preconditions/reception.py +125 -0
- tnfr/operators/preconditions/resonance.py +364 -0
- tnfr/operators/registry.py +74 -0
- tnfr/operators/registry.pyi +9 -0
- tnfr/operators/remesh.py +1415 -91
- tnfr/operators/remesh.pyi +26 -0
- tnfr/operators/structural_units.py +268 -0
- tnfr/operators/unified_grammar.py +105 -0
- tnfr/parallel/__init__.py +54 -0
- tnfr/parallel/auto_scaler.py +234 -0
- tnfr/parallel/distributed.py +384 -0
- tnfr/parallel/engine.py +238 -0
- tnfr/parallel/gpu_engine.py +420 -0
- tnfr/parallel/monitoring.py +248 -0
- tnfr/parallel/partitioner.py +459 -0
- tnfr/py.typed +0 -0
- tnfr/recipes/__init__.py +22 -0
- tnfr/recipes/cookbook.py +743 -0
- tnfr/rng.py +75 -151
- tnfr/rng.pyi +26 -0
- tnfr/schemas/__init__.py +8 -0
- tnfr/schemas/grammar.json +94 -0
- tnfr/sdk/__init__.py +107 -0
- tnfr/sdk/__init__.pyi +19 -0
- tnfr/sdk/adaptive_system.py +173 -0
- tnfr/sdk/adaptive_system.pyi +21 -0
- tnfr/sdk/builders.py +370 -0
- tnfr/sdk/builders.pyi +51 -0
- tnfr/sdk/fluent.py +1121 -0
- tnfr/sdk/fluent.pyi +74 -0
- tnfr/sdk/templates.py +342 -0
- tnfr/sdk/templates.pyi +41 -0
- tnfr/sdk/utils.py +341 -0
- tnfr/secure_config.py +46 -0
- tnfr/security/__init__.py +70 -0
- tnfr/security/database.py +514 -0
- tnfr/security/subprocess.py +503 -0
- tnfr/security/validation.py +290 -0
- tnfr/selector.py +59 -22
- tnfr/selector.pyi +19 -0
- tnfr/sense.py +92 -67
- tnfr/sense.pyi +23 -0
- tnfr/services/__init__.py +17 -0
- tnfr/services/orchestrator.py +325 -0
- tnfr/sparse/__init__.py +39 -0
- tnfr/sparse/representations.py +492 -0
- tnfr/structural.py +639 -263
- tnfr/structural.pyi +83 -0
- tnfr/telemetry/__init__.py +35 -0
- tnfr/telemetry/cache_metrics.py +226 -0
- tnfr/telemetry/cache_metrics.pyi +64 -0
- tnfr/telemetry/nu_f.py +422 -0
- tnfr/telemetry/nu_f.pyi +108 -0
- tnfr/telemetry/verbosity.py +36 -0
- tnfr/telemetry/verbosity.pyi +15 -0
- tnfr/tokens.py +2 -4
- tnfr/tokens.pyi +36 -0
- tnfr/tools/__init__.py +20 -0
- tnfr/tools/domain_templates.py +478 -0
- tnfr/tools/sequence_generator.py +846 -0
- tnfr/topology/__init__.py +13 -0
- tnfr/topology/asymmetry.py +151 -0
- tnfr/trace.py +300 -126
- tnfr/trace.pyi +42 -0
- tnfr/tutorials/__init__.py +38 -0
- tnfr/tutorials/autonomous_evolution.py +285 -0
- tnfr/tutorials/interactive.py +1576 -0
- tnfr/tutorials/structural_metabolism.py +238 -0
- tnfr/types.py +743 -12
- tnfr/types.pyi +357 -0
- tnfr/units.py +68 -0
- tnfr/units.pyi +13 -0
- tnfr/utils/__init__.py +282 -0
- tnfr/utils/__init__.pyi +215 -0
- tnfr/utils/cache.py +4223 -0
- tnfr/utils/cache.pyi +470 -0
- tnfr/{callback_utils.py → utils/callbacks.py} +26 -39
- tnfr/utils/callbacks.pyi +49 -0
- tnfr/utils/chunks.py +108 -0
- tnfr/utils/chunks.pyi +22 -0
- tnfr/utils/data.py +428 -0
- tnfr/utils/data.pyi +74 -0
- tnfr/utils/graph.py +85 -0
- tnfr/utils/graph.pyi +10 -0
- tnfr/utils/init.py +821 -0
- tnfr/utils/init.pyi +80 -0
- tnfr/utils/io.py +559 -0
- tnfr/utils/io.pyi +66 -0
- tnfr/{helpers → utils}/numeric.py +51 -24
- tnfr/utils/numeric.pyi +21 -0
- tnfr/validation/__init__.py +257 -0
- tnfr/validation/__init__.pyi +85 -0
- tnfr/validation/compatibility.py +460 -0
- tnfr/validation/compatibility.pyi +6 -0
- tnfr/validation/config.py +73 -0
- tnfr/validation/graph.py +139 -0
- tnfr/validation/graph.pyi +18 -0
- tnfr/validation/input_validation.py +755 -0
- tnfr/validation/invariants.py +712 -0
- tnfr/validation/rules.py +253 -0
- tnfr/validation/rules.pyi +44 -0
- tnfr/validation/runtime.py +279 -0
- tnfr/validation/runtime.pyi +28 -0
- tnfr/validation/sequence_validator.py +162 -0
- tnfr/validation/soft_filters.py +170 -0
- tnfr/validation/soft_filters.pyi +32 -0
- tnfr/validation/spectral.py +164 -0
- tnfr/validation/spectral.pyi +42 -0
- tnfr/validation/validator.py +1266 -0
- tnfr/validation/window.py +39 -0
- tnfr/validation/window.pyi +1 -0
- tnfr/visualization/__init__.py +98 -0
- tnfr/visualization/cascade_viz.py +256 -0
- tnfr/visualization/hierarchy.py +284 -0
- tnfr/visualization/sequence_plotter.py +784 -0
- tnfr/viz/__init__.py +60 -0
- tnfr/viz/matplotlib.py +278 -0
- tnfr/viz/matplotlib.pyi +35 -0
- tnfr-8.5.0.dist-info/METADATA +573 -0
- tnfr-8.5.0.dist-info/RECORD +353 -0
- {tnfr-4.5.2.dist-info → tnfr-8.5.0.dist-info}/entry_points.txt +1 -0
- {tnfr-4.5.2.dist-info → tnfr-8.5.0.dist-info}/licenses/LICENSE.md +1 -1
- tnfr/collections_utils.py +0 -300
- tnfr/config.py +0 -32
- tnfr/grammar.py +0 -344
- tnfr/graph_utils.py +0 -84
- tnfr/helpers/__init__.py +0 -71
- tnfr/import_utils.py +0 -228
- tnfr/json_utils.py +0 -162
- tnfr/logging_utils.py +0 -116
- tnfr/presets.py +0 -60
- tnfr/validators.py +0 -84
- tnfr/value_utils.py +0 -59
- tnfr-4.5.2.dist-info/METADATA +0 -379
- tnfr-4.5.2.dist-info/RECORD +0 -67
- {tnfr-4.5.2.dist-info → tnfr-8.5.0.dist-info}/WHEEL +0 -0
- {tnfr-4.5.2.dist-info → tnfr-8.5.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,1266 @@
|
|
|
1
|
+
"""Unified TNFR Validation Pipeline.
|
|
2
|
+
|
|
3
|
+
This module provides the TNFRValidator class which serves as the canonical
|
|
4
|
+
entry point for all TNFR validation operations. It integrates:
|
|
5
|
+
- Invariant validation (10 canonical TNFR invariants)
|
|
6
|
+
- Input validation (parameters, types, bounds)
|
|
7
|
+
- Graph validation (structure, coherence)
|
|
8
|
+
- Runtime validation (canonical clamps, contracts)
|
|
9
|
+
- Security validation (injection prevention, type safety)
|
|
10
|
+
- Operator precondition validation
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import warnings
|
|
16
|
+
from typing import Any, Mapping, Optional
|
|
17
|
+
|
|
18
|
+
from .invariants import (
|
|
19
|
+
InvariantSeverity,
|
|
20
|
+
InvariantViolation,
|
|
21
|
+
Invariant1_EPIOnlyThroughOperators,
|
|
22
|
+
Invariant2_VfInHzStr,
|
|
23
|
+
Invariant3_DNFRSemantics,
|
|
24
|
+
Invariant4_OperatorClosure,
|
|
25
|
+
Invariant5_ExplicitPhaseChecks,
|
|
26
|
+
Invariant6_NodeBirthCollapse,
|
|
27
|
+
Invariant7_OperationalFractality,
|
|
28
|
+
Invariant8_ControlledDeterminism,
|
|
29
|
+
Invariant9_StructuralMetrics,
|
|
30
|
+
Invariant10_DomainNeutrality,
|
|
31
|
+
TNFRInvariant,
|
|
32
|
+
)
|
|
33
|
+
from ..types import Glyph, NodeId, TNFRGraph
|
|
34
|
+
|
|
35
|
+
__all__ = [
|
|
36
|
+
"TNFRValidator",
|
|
37
|
+
"TNFRValidationError",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class TNFRValidator:
|
|
42
|
+
"""Unified TNFR Validation Pipeline.
|
|
43
|
+
|
|
44
|
+
This class serves as the single entry point for all TNFR validation operations,
|
|
45
|
+
consolidating scattered validation logic into a coherent pipeline that enforces
|
|
46
|
+
all canonical TNFR invariants.
|
|
47
|
+
|
|
48
|
+
Features
|
|
49
|
+
--------
|
|
50
|
+
- Validates 10 canonical TNFR invariants
|
|
51
|
+
- Input validation with security checks
|
|
52
|
+
- Graph structure and coherence validation
|
|
53
|
+
- Runtime canonical validation
|
|
54
|
+
- Operator precondition checking
|
|
55
|
+
- Comprehensive reporting (text, JSON, HTML)
|
|
56
|
+
- Optional result caching for performance
|
|
57
|
+
|
|
58
|
+
Examples
|
|
59
|
+
--------
|
|
60
|
+
>>> validator = TNFRValidator()
|
|
61
|
+
>>> violations = validator.validate_graph(graph)
|
|
62
|
+
>>> if violations:
|
|
63
|
+
... print(validator.generate_report(violations))
|
|
64
|
+
|
|
65
|
+
>>> # Validate inputs before operator application
|
|
66
|
+
>>> validator.validate_inputs(epi=0.5, vf=1.0, theta=0.0, config=G.graph)
|
|
67
|
+
|
|
68
|
+
>>> # Validate operator preconditions
|
|
69
|
+
>>> validator.validate_operator_preconditions(G, node, "emission")
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def __init__(
|
|
73
|
+
self,
|
|
74
|
+
phase_coupling_threshold: float | None = None,
|
|
75
|
+
enable_input_validation: bool = True,
|
|
76
|
+
enable_graph_validation: bool = True,
|
|
77
|
+
enable_runtime_validation: bool = True,
|
|
78
|
+
) -> None:
|
|
79
|
+
"""Initialize unified TNFR validator.
|
|
80
|
+
|
|
81
|
+
Parameters
|
|
82
|
+
----------
|
|
83
|
+
phase_coupling_threshold : float, optional
|
|
84
|
+
Threshold for phase difference in coupled nodes (default: π/2).
|
|
85
|
+
enable_input_validation : bool, optional
|
|
86
|
+
Enable input validation checks (default: True).
|
|
87
|
+
enable_graph_validation : bool, optional
|
|
88
|
+
Enable graph structure validation (default: True).
|
|
89
|
+
enable_runtime_validation : bool, optional
|
|
90
|
+
Enable runtime canonical validation (default: True).
|
|
91
|
+
"""
|
|
92
|
+
# Initialize core invariant validators
|
|
93
|
+
self._invariant_validators: list[TNFRInvariant] = [
|
|
94
|
+
Invariant1_EPIOnlyThroughOperators(),
|
|
95
|
+
Invariant2_VfInHzStr(),
|
|
96
|
+
Invariant3_DNFRSemantics(),
|
|
97
|
+
Invariant4_OperatorClosure(),
|
|
98
|
+
Invariant6_NodeBirthCollapse(),
|
|
99
|
+
Invariant7_OperationalFractality(),
|
|
100
|
+
Invariant8_ControlledDeterminism(),
|
|
101
|
+
Invariant9_StructuralMetrics(),
|
|
102
|
+
Invariant10_DomainNeutrality(),
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
# Initialize phase validator with custom threshold if provided
|
|
106
|
+
if phase_coupling_threshold is not None:
|
|
107
|
+
self._invariant_validators.append(
|
|
108
|
+
Invariant5_ExplicitPhaseChecks(phase_coupling_threshold)
|
|
109
|
+
)
|
|
110
|
+
else:
|
|
111
|
+
self._invariant_validators.append(Invariant5_ExplicitPhaseChecks())
|
|
112
|
+
|
|
113
|
+
self._custom_validators: list[TNFRInvariant] = []
|
|
114
|
+
|
|
115
|
+
# Validation pipeline configuration
|
|
116
|
+
self._enable_input_validation = enable_input_validation
|
|
117
|
+
self._enable_graph_validation = enable_graph_validation
|
|
118
|
+
self._enable_runtime_validation = enable_runtime_validation
|
|
119
|
+
|
|
120
|
+
# Cache for validation results (graph_id -> violations)
|
|
121
|
+
self._validation_cache: dict[int, list[InvariantViolation]] = {}
|
|
122
|
+
self._cache_enabled = False
|
|
123
|
+
|
|
124
|
+
def add_custom_validator(self, validator: TNFRInvariant) -> None:
|
|
125
|
+
"""Add custom invariant validator.
|
|
126
|
+
|
|
127
|
+
Parameters
|
|
128
|
+
----------
|
|
129
|
+
validator : TNFRInvariant
|
|
130
|
+
Custom validator implementing TNFRInvariant interface.
|
|
131
|
+
"""
|
|
132
|
+
self._custom_validators.append(validator)
|
|
133
|
+
|
|
134
|
+
def enable_cache(self, enabled: bool = True) -> None:
|
|
135
|
+
"""Enable or disable validation result caching.
|
|
136
|
+
|
|
137
|
+
Parameters
|
|
138
|
+
----------
|
|
139
|
+
enabled : bool
|
|
140
|
+
Whether to enable caching (default: True).
|
|
141
|
+
"""
|
|
142
|
+
self._cache_enabled = enabled
|
|
143
|
+
if not enabled:
|
|
144
|
+
self._validation_cache.clear()
|
|
145
|
+
|
|
146
|
+
def clear_cache(self) -> None:
|
|
147
|
+
"""Clear the validation result cache."""
|
|
148
|
+
self._validation_cache.clear()
|
|
149
|
+
|
|
150
|
+
def validate(
|
|
151
|
+
self,
|
|
152
|
+
graph: TNFRGraph | None = None,
|
|
153
|
+
*,
|
|
154
|
+
epi: Any = None,
|
|
155
|
+
vf: Any = None,
|
|
156
|
+
theta: Any = None,
|
|
157
|
+
dnfr: Any = None,
|
|
158
|
+
node_id: NodeId | None = None,
|
|
159
|
+
operator: str | None = None,
|
|
160
|
+
include_invariants: bool = True,
|
|
161
|
+
include_graph_structure: bool = True,
|
|
162
|
+
include_runtime: bool = False,
|
|
163
|
+
raise_on_error: bool = False,
|
|
164
|
+
) -> dict[str, Any]:
|
|
165
|
+
"""Comprehensive unified validation pipeline (single entry point).
|
|
166
|
+
|
|
167
|
+
This method provides a single entry point for all TNFR validation needs,
|
|
168
|
+
consolidating input validation, graph validation, invariant checking,
|
|
169
|
+
and operator preconditions into one call.
|
|
170
|
+
|
|
171
|
+
Parameters
|
|
172
|
+
----------
|
|
173
|
+
graph : TNFRGraph, optional
|
|
174
|
+
Graph to validate (required for graph/invariant validation).
|
|
175
|
+
epi : Any, optional
|
|
176
|
+
EPI value to validate.
|
|
177
|
+
vf : Any, optional
|
|
178
|
+
Structural frequency (νf) to validate.
|
|
179
|
+
theta : Any, optional
|
|
180
|
+
Phase (θ) to validate.
|
|
181
|
+
dnfr : Any, optional
|
|
182
|
+
ΔNFR value to validate.
|
|
183
|
+
node_id : NodeId, optional
|
|
184
|
+
Node ID to validate (required for operator preconditions).
|
|
185
|
+
operator : str, optional
|
|
186
|
+
Operator name to validate preconditions for.
|
|
187
|
+
include_invariants : bool, optional
|
|
188
|
+
Include invariant validation (default: True).
|
|
189
|
+
include_graph_structure : bool, optional
|
|
190
|
+
Include graph structure validation (default: True).
|
|
191
|
+
include_runtime : bool, optional
|
|
192
|
+
Include runtime canonical validation (default: False).
|
|
193
|
+
raise_on_error : bool, optional
|
|
194
|
+
Whether to raise on first error (default: False).
|
|
195
|
+
|
|
196
|
+
Returns
|
|
197
|
+
-------
|
|
198
|
+
dict[str, Any]
|
|
199
|
+
Comprehensive validation results including:
|
|
200
|
+
- 'passed': bool - Overall validation status
|
|
201
|
+
- 'inputs': dict - Input validation results
|
|
202
|
+
- 'graph_structure': dict - Graph structure validation results
|
|
203
|
+
- 'runtime': dict - Runtime validation results
|
|
204
|
+
- 'invariants': list - Invariant violations
|
|
205
|
+
- 'operator_preconditions': bool - Operator precondition status
|
|
206
|
+
- 'errors': list - Any errors encountered
|
|
207
|
+
|
|
208
|
+
Examples
|
|
209
|
+
--------
|
|
210
|
+
>>> validator = TNFRValidator()
|
|
211
|
+
>>> # Validate graph with inputs
|
|
212
|
+
>>> result = validator.validate(
|
|
213
|
+
... graph=G,
|
|
214
|
+
... epi=0.5,
|
|
215
|
+
... vf=1.0,
|
|
216
|
+
... include_invariants=True
|
|
217
|
+
... )
|
|
218
|
+
>>> if not result['passed']:
|
|
219
|
+
... print(f"Validation failed: {result['errors']}")
|
|
220
|
+
|
|
221
|
+
>>> # Validate operator preconditions
|
|
222
|
+
>>> result = validator.validate(
|
|
223
|
+
... graph=G,
|
|
224
|
+
... node_id="node_1",
|
|
225
|
+
... operator="emission"
|
|
226
|
+
... )
|
|
227
|
+
>>> if result['operator_preconditions']:
|
|
228
|
+
... # Apply operator
|
|
229
|
+
... pass
|
|
230
|
+
"""
|
|
231
|
+
results: dict[str, Any] = {
|
|
232
|
+
"passed": True,
|
|
233
|
+
"inputs": {},
|
|
234
|
+
"graph_structure": None,
|
|
235
|
+
"runtime": None,
|
|
236
|
+
"invariants": [],
|
|
237
|
+
"operator_preconditions": None,
|
|
238
|
+
"errors": [],
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
config = graph.graph if graph is not None else None
|
|
242
|
+
|
|
243
|
+
# Input validation
|
|
244
|
+
if epi is not None or vf is not None or theta is not None or dnfr is not None:
|
|
245
|
+
try:
|
|
246
|
+
results["inputs"] = self.validate_inputs(
|
|
247
|
+
epi=epi,
|
|
248
|
+
vf=vf,
|
|
249
|
+
theta=theta,
|
|
250
|
+
dnfr=dnfr,
|
|
251
|
+
node_id=node_id,
|
|
252
|
+
config=config,
|
|
253
|
+
raise_on_error=raise_on_error,
|
|
254
|
+
)
|
|
255
|
+
if "error" in results["inputs"]:
|
|
256
|
+
results["passed"] = False
|
|
257
|
+
results["errors"].append(
|
|
258
|
+
f"Input validation: {results['inputs']['error']}"
|
|
259
|
+
)
|
|
260
|
+
except Exception as e:
|
|
261
|
+
results["passed"] = False
|
|
262
|
+
results["errors"].append(f"Input validation failed: {str(e)}")
|
|
263
|
+
if raise_on_error:
|
|
264
|
+
raise
|
|
265
|
+
|
|
266
|
+
# Graph validation
|
|
267
|
+
if graph is not None:
|
|
268
|
+
# Graph structure validation
|
|
269
|
+
if include_graph_structure:
|
|
270
|
+
try:
|
|
271
|
+
results["graph_structure"] = self.validate_graph_structure(
|
|
272
|
+
graph,
|
|
273
|
+
raise_on_error=raise_on_error,
|
|
274
|
+
)
|
|
275
|
+
if not results["graph_structure"].get("passed", False):
|
|
276
|
+
results["passed"] = False
|
|
277
|
+
results["errors"].append(
|
|
278
|
+
f"Graph structure: {results['graph_structure'].get('error', 'Failed')}"
|
|
279
|
+
)
|
|
280
|
+
except Exception as e:
|
|
281
|
+
results["passed"] = False
|
|
282
|
+
results["errors"].append(
|
|
283
|
+
f"Graph structure validation failed: {str(e)}"
|
|
284
|
+
)
|
|
285
|
+
if raise_on_error:
|
|
286
|
+
raise
|
|
287
|
+
|
|
288
|
+
# Runtime canonical validation
|
|
289
|
+
if include_runtime:
|
|
290
|
+
try:
|
|
291
|
+
results["runtime"] = self.validate_runtime_canonical(
|
|
292
|
+
graph,
|
|
293
|
+
raise_on_error=raise_on_error,
|
|
294
|
+
)
|
|
295
|
+
if not results["runtime"].get("passed", False):
|
|
296
|
+
results["passed"] = False
|
|
297
|
+
results["errors"].append(
|
|
298
|
+
f"Runtime validation: {results['runtime'].get('error', 'Failed')}"
|
|
299
|
+
)
|
|
300
|
+
except Exception as e:
|
|
301
|
+
results["passed"] = False
|
|
302
|
+
results["errors"].append(f"Runtime validation failed: {str(e)}")
|
|
303
|
+
if raise_on_error:
|
|
304
|
+
raise
|
|
305
|
+
|
|
306
|
+
# Invariant validation
|
|
307
|
+
if include_invariants:
|
|
308
|
+
try:
|
|
309
|
+
violations = self.validate_graph(
|
|
310
|
+
graph,
|
|
311
|
+
include_graph_validation=False, # Already done above
|
|
312
|
+
include_runtime_validation=False, # Already done above
|
|
313
|
+
)
|
|
314
|
+
results["invariants"] = violations
|
|
315
|
+
if violations:
|
|
316
|
+
# Check if there are any ERROR or CRITICAL violations
|
|
317
|
+
critical_violations = [
|
|
318
|
+
v
|
|
319
|
+
for v in violations
|
|
320
|
+
if v.severity
|
|
321
|
+
in (InvariantSeverity.ERROR, InvariantSeverity.CRITICAL)
|
|
322
|
+
]
|
|
323
|
+
if critical_violations:
|
|
324
|
+
results["passed"] = False
|
|
325
|
+
results["errors"].append(
|
|
326
|
+
f"{len(critical_violations)} critical invariant violations found"
|
|
327
|
+
)
|
|
328
|
+
except Exception as e:
|
|
329
|
+
results["passed"] = False
|
|
330
|
+
results["errors"].append(f"Invariant validation failed: {str(e)}")
|
|
331
|
+
if raise_on_error:
|
|
332
|
+
raise
|
|
333
|
+
|
|
334
|
+
# Operator preconditions validation
|
|
335
|
+
if operator is not None and node_id is not None:
|
|
336
|
+
try:
|
|
337
|
+
results["operator_preconditions"] = (
|
|
338
|
+
self.validate_operator_preconditions(
|
|
339
|
+
graph,
|
|
340
|
+
node_id,
|
|
341
|
+
operator,
|
|
342
|
+
raise_on_error=raise_on_error,
|
|
343
|
+
)
|
|
344
|
+
)
|
|
345
|
+
if not results["operator_preconditions"]:
|
|
346
|
+
results["passed"] = False
|
|
347
|
+
results["errors"].append(
|
|
348
|
+
f"Operator '{operator}' preconditions not met for node {node_id}"
|
|
349
|
+
)
|
|
350
|
+
except Exception as e:
|
|
351
|
+
results["passed"] = False
|
|
352
|
+
results["errors"].append(
|
|
353
|
+
f"Operator precondition validation failed: {str(e)}"
|
|
354
|
+
)
|
|
355
|
+
if raise_on_error:
|
|
356
|
+
raise
|
|
357
|
+
|
|
358
|
+
return results
|
|
359
|
+
|
|
360
|
+
def validate_inputs(
|
|
361
|
+
self,
|
|
362
|
+
*,
|
|
363
|
+
epi: Any = None,
|
|
364
|
+
vf: Any = None,
|
|
365
|
+
theta: Any = None,
|
|
366
|
+
dnfr: Any = None,
|
|
367
|
+
node_id: Any = None,
|
|
368
|
+
glyph: Any = None,
|
|
369
|
+
graph: Any = None,
|
|
370
|
+
config: Mapping[str, Any] | None = None,
|
|
371
|
+
raise_on_error: bool = True,
|
|
372
|
+
) -> dict[str, Any]:
|
|
373
|
+
"""Validate structural operator inputs.
|
|
374
|
+
|
|
375
|
+
This method consolidates input validation for all TNFR structural parameters,
|
|
376
|
+
enforcing type safety, bounds checking, and security constraints.
|
|
377
|
+
|
|
378
|
+
Parameters
|
|
379
|
+
----------
|
|
380
|
+
epi : Any, optional
|
|
381
|
+
EPI (Primary Information Structure) value to validate.
|
|
382
|
+
vf : Any, optional
|
|
383
|
+
νf (structural frequency) value to validate.
|
|
384
|
+
theta : Any, optional
|
|
385
|
+
θ (phase) value to validate.
|
|
386
|
+
dnfr : Any, optional
|
|
387
|
+
ΔNFR (reorganization operator) value to validate.
|
|
388
|
+
node_id : Any, optional
|
|
389
|
+
Node identifier to validate.
|
|
390
|
+
glyph : Any, optional
|
|
391
|
+
Glyph enumeration to validate.
|
|
392
|
+
graph : Any, optional
|
|
393
|
+
TNFRGraph to validate.
|
|
394
|
+
config : Mapping[str, Any], optional
|
|
395
|
+
Configuration for bounds checking.
|
|
396
|
+
raise_on_error : bool, optional
|
|
397
|
+
Whether to raise exception on validation failure (default: True).
|
|
398
|
+
|
|
399
|
+
Returns
|
|
400
|
+
-------
|
|
401
|
+
dict[str, Any]
|
|
402
|
+
Dictionary with validation results for each parameter.
|
|
403
|
+
Keys: parameter names, Values: validation status or validated values.
|
|
404
|
+
|
|
405
|
+
Raises
|
|
406
|
+
------
|
|
407
|
+
ValidationError
|
|
408
|
+
If any validation fails and raise_on_error is True.
|
|
409
|
+
|
|
410
|
+
Examples
|
|
411
|
+
--------
|
|
412
|
+
>>> validator = TNFRValidator()
|
|
413
|
+
>>> validator.validate_inputs(epi=0.5, vf=1.0, theta=0.0)
|
|
414
|
+
{'epi': 0.5, 'vf': 1.0, 'theta': 0.0}
|
|
415
|
+
"""
|
|
416
|
+
if not self._enable_input_validation:
|
|
417
|
+
return {}
|
|
418
|
+
|
|
419
|
+
from .input_validation import (
|
|
420
|
+
validate_epi_value,
|
|
421
|
+
validate_vf_value,
|
|
422
|
+
validate_theta_value,
|
|
423
|
+
validate_dnfr_value,
|
|
424
|
+
validate_node_id,
|
|
425
|
+
validate_glyph,
|
|
426
|
+
validate_tnfr_graph,
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
results = {}
|
|
430
|
+
|
|
431
|
+
try:
|
|
432
|
+
if epi is not None:
|
|
433
|
+
results["epi"] = validate_epi_value(epi, config=config)
|
|
434
|
+
|
|
435
|
+
if vf is not None:
|
|
436
|
+
results["vf"] = validate_vf_value(vf, config=config)
|
|
437
|
+
|
|
438
|
+
if theta is not None:
|
|
439
|
+
results["theta"] = validate_theta_value(theta)
|
|
440
|
+
|
|
441
|
+
if dnfr is not None:
|
|
442
|
+
results["dnfr"] = validate_dnfr_value(dnfr, config=config)
|
|
443
|
+
|
|
444
|
+
if node_id is not None:
|
|
445
|
+
results["node_id"] = validate_node_id(node_id)
|
|
446
|
+
|
|
447
|
+
if glyph is not None:
|
|
448
|
+
results["glyph"] = validate_glyph(glyph)
|
|
449
|
+
|
|
450
|
+
if graph is not None:
|
|
451
|
+
results["graph"] = validate_tnfr_graph(graph)
|
|
452
|
+
|
|
453
|
+
except Exception as e:
|
|
454
|
+
if raise_on_error:
|
|
455
|
+
raise
|
|
456
|
+
results["error"] = str(e)
|
|
457
|
+
|
|
458
|
+
return results
|
|
459
|
+
|
|
460
|
+
def validate_operator_preconditions(
|
|
461
|
+
self,
|
|
462
|
+
graph: TNFRGraph,
|
|
463
|
+
node: NodeId,
|
|
464
|
+
operator: str,
|
|
465
|
+
raise_on_error: bool = True,
|
|
466
|
+
) -> bool:
|
|
467
|
+
"""Validate operator preconditions before application.
|
|
468
|
+
|
|
469
|
+
Each TNFR structural operator has specific requirements that must be met
|
|
470
|
+
before execution to maintain structural invariants.
|
|
471
|
+
|
|
472
|
+
Parameters
|
|
473
|
+
----------
|
|
474
|
+
graph : TNFRGraph
|
|
475
|
+
Graph containing the target node.
|
|
476
|
+
node : NodeId
|
|
477
|
+
Target node for operator application.
|
|
478
|
+
operator : str
|
|
479
|
+
Name of the operator to validate (e.g., "emission", "coherence").
|
|
480
|
+
raise_on_error : bool, optional
|
|
481
|
+
Whether to raise exception on failure (default: True).
|
|
482
|
+
|
|
483
|
+
Returns
|
|
484
|
+
-------
|
|
485
|
+
bool
|
|
486
|
+
True if preconditions are met, False otherwise.
|
|
487
|
+
|
|
488
|
+
Raises
|
|
489
|
+
------
|
|
490
|
+
OperatorPreconditionError
|
|
491
|
+
If preconditions are not met and raise_on_error is True.
|
|
492
|
+
|
|
493
|
+
Examples
|
|
494
|
+
--------
|
|
495
|
+
>>> validator = TNFRValidator()
|
|
496
|
+
>>> if validator.validate_operator_preconditions(G, node, "emission"):
|
|
497
|
+
... # Apply emission operator
|
|
498
|
+
... pass
|
|
499
|
+
"""
|
|
500
|
+
from ..operators import preconditions
|
|
501
|
+
|
|
502
|
+
validator_map = {
|
|
503
|
+
"emission": preconditions.validate_emission,
|
|
504
|
+
"reception": preconditions.validate_reception,
|
|
505
|
+
"coherence": preconditions.validate_coherence,
|
|
506
|
+
"dissonance": preconditions.validate_dissonance,
|
|
507
|
+
"coupling": preconditions.validate_coupling,
|
|
508
|
+
"resonance": preconditions.validate_resonance,
|
|
509
|
+
"silence": preconditions.validate_silence,
|
|
510
|
+
"expansion": preconditions.validate_expansion,
|
|
511
|
+
"contraction": preconditions.validate_contraction,
|
|
512
|
+
"self_organization": preconditions.validate_self_organization,
|
|
513
|
+
"mutation": preconditions.validate_mutation,
|
|
514
|
+
"transition": preconditions.validate_transition,
|
|
515
|
+
"recursivity": preconditions.validate_recursivity,
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
validator_func = validator_map.get(operator.lower())
|
|
519
|
+
if validator_func is None:
|
|
520
|
+
if raise_on_error:
|
|
521
|
+
raise ValueError(f"Unknown operator: {operator}")
|
|
522
|
+
return False
|
|
523
|
+
|
|
524
|
+
try:
|
|
525
|
+
validator_func(graph, node)
|
|
526
|
+
return True
|
|
527
|
+
except Exception as e:
|
|
528
|
+
if raise_on_error:
|
|
529
|
+
raise
|
|
530
|
+
return False
|
|
531
|
+
|
|
532
|
+
def validate_graph_structure(
|
|
533
|
+
self,
|
|
534
|
+
graph: TNFRGraph,
|
|
535
|
+
raise_on_error: bool = True,
|
|
536
|
+
) -> dict[str, Any]:
|
|
537
|
+
"""Validate graph structure and coherence.
|
|
538
|
+
|
|
539
|
+
Performs structural validation including:
|
|
540
|
+
- Node attribute completeness
|
|
541
|
+
- EPI bounds and grid uniformity
|
|
542
|
+
- Structural frequency ranges
|
|
543
|
+
- Coherence metrics
|
|
544
|
+
|
|
545
|
+
Parameters
|
|
546
|
+
----------
|
|
547
|
+
graph : TNFRGraph
|
|
548
|
+
Graph to validate.
|
|
549
|
+
raise_on_error : bool, optional
|
|
550
|
+
Whether to raise exception on failure (default: True).
|
|
551
|
+
|
|
552
|
+
Returns
|
|
553
|
+
-------
|
|
554
|
+
dict[str, Any]
|
|
555
|
+
Validation results including passed checks and any errors.
|
|
556
|
+
|
|
557
|
+
Raises
|
|
558
|
+
------
|
|
559
|
+
ValueError
|
|
560
|
+
If structural validation fails and raise_on_error is True.
|
|
561
|
+
"""
|
|
562
|
+
if not self._enable_graph_validation:
|
|
563
|
+
return {"passed": True, "message": "Graph validation disabled"}
|
|
564
|
+
|
|
565
|
+
from .graph import run_validators
|
|
566
|
+
|
|
567
|
+
try:
|
|
568
|
+
run_validators(graph)
|
|
569
|
+
return {"passed": True, "message": "Graph structure valid"}
|
|
570
|
+
except Exception as e:
|
|
571
|
+
if raise_on_error:
|
|
572
|
+
raise
|
|
573
|
+
return {"passed": False, "error": str(e)}
|
|
574
|
+
|
|
575
|
+
def validate_runtime_canonical(
|
|
576
|
+
self,
|
|
577
|
+
graph: TNFRGraph,
|
|
578
|
+
raise_on_error: bool = True,
|
|
579
|
+
) -> dict[str, Any]:
|
|
580
|
+
"""Validate runtime canonical constraints.
|
|
581
|
+
|
|
582
|
+
Applies canonical clamps and validates graph contracts at runtime.
|
|
583
|
+
|
|
584
|
+
Parameters
|
|
585
|
+
----------
|
|
586
|
+
graph : TNFRGraph
|
|
587
|
+
Graph to validate.
|
|
588
|
+
raise_on_error : bool, optional
|
|
589
|
+
Whether to raise exception on failure (default: True).
|
|
590
|
+
|
|
591
|
+
Returns
|
|
592
|
+
-------
|
|
593
|
+
dict[str, Any]
|
|
594
|
+
Validation results.
|
|
595
|
+
|
|
596
|
+
Raises
|
|
597
|
+
------
|
|
598
|
+
Exception
|
|
599
|
+
If runtime validation fails and raise_on_error is True.
|
|
600
|
+
"""
|
|
601
|
+
if not self._enable_runtime_validation:
|
|
602
|
+
return {"passed": True, "message": "Runtime validation disabled"}
|
|
603
|
+
|
|
604
|
+
from .runtime import validate_canon
|
|
605
|
+
|
|
606
|
+
try:
|
|
607
|
+
outcome = validate_canon(graph)
|
|
608
|
+
return {
|
|
609
|
+
"passed": outcome.passed,
|
|
610
|
+
"summary": outcome.summary,
|
|
611
|
+
"artifacts": outcome.artifacts,
|
|
612
|
+
}
|
|
613
|
+
except Exception as e:
|
|
614
|
+
if raise_on_error:
|
|
615
|
+
raise
|
|
616
|
+
return {"passed": False, "error": str(e)}
|
|
617
|
+
|
|
618
|
+
def validate_graph(
|
|
619
|
+
self,
|
|
620
|
+
graph: TNFRGraph,
|
|
621
|
+
severity_filter: Optional[InvariantSeverity] = None,
|
|
622
|
+
use_cache: bool = True,
|
|
623
|
+
include_graph_validation: bool = True,
|
|
624
|
+
include_runtime_validation: bool = False,
|
|
625
|
+
) -> list[InvariantViolation]:
|
|
626
|
+
"""Validate graph against all TNFR invariants (unified pipeline).
|
|
627
|
+
|
|
628
|
+
This is the main entry point for comprehensive graph validation,
|
|
629
|
+
integrating all validation layers:
|
|
630
|
+
- Invariant validation (10 canonical TNFR invariants)
|
|
631
|
+
- Optional graph structure validation
|
|
632
|
+
- Optional runtime canonical validation
|
|
633
|
+
|
|
634
|
+
Parameters
|
|
635
|
+
----------
|
|
636
|
+
graph : TNFRGraph
|
|
637
|
+
Graph to validate against TNFR invariants.
|
|
638
|
+
severity_filter : InvariantSeverity, optional
|
|
639
|
+
Only return violations of this severity level.
|
|
640
|
+
use_cache : bool, optional
|
|
641
|
+
Whether to use cached results if available (default: True).
|
|
642
|
+
include_graph_validation : bool, optional
|
|
643
|
+
Include graph structure validation (default: True).
|
|
644
|
+
include_runtime_validation : bool, optional
|
|
645
|
+
Include runtime canonical validation (default: False).
|
|
646
|
+
|
|
647
|
+
Returns
|
|
648
|
+
-------
|
|
649
|
+
list[InvariantViolation]
|
|
650
|
+
List of detected violations.
|
|
651
|
+
|
|
652
|
+
Examples
|
|
653
|
+
--------
|
|
654
|
+
>>> validator = TNFRValidator()
|
|
655
|
+
>>> violations = validator.validate_graph(graph)
|
|
656
|
+
>>> if violations:
|
|
657
|
+
... print(validator.generate_report(violations))
|
|
658
|
+
"""
|
|
659
|
+
# Check cache if enabled
|
|
660
|
+
if self._cache_enabled and use_cache:
|
|
661
|
+
graph_id = id(graph)
|
|
662
|
+
if graph_id in self._validation_cache:
|
|
663
|
+
all_violations = self._validation_cache[graph_id]
|
|
664
|
+
# Apply severity filter if specified
|
|
665
|
+
if severity_filter:
|
|
666
|
+
return [v for v in all_violations if v.severity == severity_filter]
|
|
667
|
+
return all_violations
|
|
668
|
+
|
|
669
|
+
all_violations: list[InvariantViolation] = []
|
|
670
|
+
|
|
671
|
+
# Run graph structure validation if enabled
|
|
672
|
+
if include_graph_validation and self._enable_graph_validation:
|
|
673
|
+
try:
|
|
674
|
+
result = self.validate_graph_structure(graph, raise_on_error=False)
|
|
675
|
+
if not result.get("passed", False):
|
|
676
|
+
all_violations.append(
|
|
677
|
+
InvariantViolation(
|
|
678
|
+
invariant_id=4, # Operator closure
|
|
679
|
+
severity=InvariantSeverity.ERROR,
|
|
680
|
+
description=f"Graph structure validation failed: {result.get('error', 'Unknown error')}",
|
|
681
|
+
suggestion="Check graph structure and node attributes",
|
|
682
|
+
)
|
|
683
|
+
)
|
|
684
|
+
except Exception as e:
|
|
685
|
+
all_violations.append(
|
|
686
|
+
InvariantViolation(
|
|
687
|
+
invariant_id=4,
|
|
688
|
+
severity=InvariantSeverity.CRITICAL,
|
|
689
|
+
description=f"Graph structure validator failed: {str(e)}",
|
|
690
|
+
suggestion="Check graph structure validator implementation",
|
|
691
|
+
)
|
|
692
|
+
)
|
|
693
|
+
|
|
694
|
+
# Run runtime canonical validation if enabled
|
|
695
|
+
if include_runtime_validation and self._enable_runtime_validation:
|
|
696
|
+
try:
|
|
697
|
+
result = self.validate_runtime_canonical(graph, raise_on_error=False)
|
|
698
|
+
if not result.get("passed", False):
|
|
699
|
+
all_violations.append(
|
|
700
|
+
InvariantViolation(
|
|
701
|
+
invariant_id=8, # Controlled determinism
|
|
702
|
+
severity=InvariantSeverity.WARNING,
|
|
703
|
+
description=f"Runtime canonical validation failed: {result.get('error', 'Unknown error')}",
|
|
704
|
+
suggestion="Check canonical clamps and runtime contracts",
|
|
705
|
+
)
|
|
706
|
+
)
|
|
707
|
+
except Exception as e:
|
|
708
|
+
all_violations.append(
|
|
709
|
+
InvariantViolation(
|
|
710
|
+
invariant_id=8,
|
|
711
|
+
severity=InvariantSeverity.WARNING,
|
|
712
|
+
description=f"Runtime validator failed: {str(e)}",
|
|
713
|
+
suggestion="Check runtime validator implementation",
|
|
714
|
+
)
|
|
715
|
+
)
|
|
716
|
+
|
|
717
|
+
# Run invariant validators
|
|
718
|
+
for validator in self._invariant_validators + self._custom_validators:
|
|
719
|
+
try:
|
|
720
|
+
violations = validator.validate(graph)
|
|
721
|
+
all_violations.extend(violations)
|
|
722
|
+
except Exception as e:
|
|
723
|
+
# If validator fails, it's a critical error
|
|
724
|
+
all_violations.append(
|
|
725
|
+
InvariantViolation(
|
|
726
|
+
invariant_id=validator.invariant_id,
|
|
727
|
+
severity=InvariantSeverity.CRITICAL,
|
|
728
|
+
description=f"Validator execution failed: {str(e)}",
|
|
729
|
+
suggestion="Check validator implementation",
|
|
730
|
+
)
|
|
731
|
+
)
|
|
732
|
+
|
|
733
|
+
# Cache results if enabled
|
|
734
|
+
if self._cache_enabled:
|
|
735
|
+
graph_id = id(graph)
|
|
736
|
+
self._validation_cache[graph_id] = all_violations.copy()
|
|
737
|
+
|
|
738
|
+
# Filtrar por severidad si se especifica
|
|
739
|
+
if severity_filter:
|
|
740
|
+
all_violations = [
|
|
741
|
+
v for v in all_violations if v.severity == severity_filter
|
|
742
|
+
]
|
|
743
|
+
|
|
744
|
+
return all_violations
|
|
745
|
+
|
|
746
|
+
def validate_and_raise(
|
|
747
|
+
self,
|
|
748
|
+
graph: TNFRGraph,
|
|
749
|
+
min_severity: InvariantSeverity = InvariantSeverity.ERROR,
|
|
750
|
+
) -> None:
|
|
751
|
+
"""Validates and raises exception if violations of minimum severity are found.
|
|
752
|
+
|
|
753
|
+
Parameters
|
|
754
|
+
----------
|
|
755
|
+
graph : TNFRGraph
|
|
756
|
+
Graph to validate.
|
|
757
|
+
min_severity : InvariantSeverity
|
|
758
|
+
Minimum severity level to trigger exception (default: ERROR).
|
|
759
|
+
|
|
760
|
+
Raises
|
|
761
|
+
------
|
|
762
|
+
TNFRValidationError
|
|
763
|
+
If violations of minimum severity or higher are found.
|
|
764
|
+
"""
|
|
765
|
+
violations = self.validate_graph(graph)
|
|
766
|
+
|
|
767
|
+
# Filter violations by minimum severity
|
|
768
|
+
severity_order = {
|
|
769
|
+
InvariantSeverity.INFO: -1,
|
|
770
|
+
InvariantSeverity.WARNING: 0,
|
|
771
|
+
InvariantSeverity.ERROR: 1,
|
|
772
|
+
InvariantSeverity.CRITICAL: 2,
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
critical_violations = [
|
|
776
|
+
v
|
|
777
|
+
for v in violations
|
|
778
|
+
if severity_order[v.severity] >= severity_order[min_severity]
|
|
779
|
+
]
|
|
780
|
+
|
|
781
|
+
if critical_violations:
|
|
782
|
+
raise TNFRValidationError(critical_violations)
|
|
783
|
+
|
|
784
|
+
def generate_report(self, violations: list[InvariantViolation]) -> str:
|
|
785
|
+
"""Genera reporte human-readable de violaciones.
|
|
786
|
+
|
|
787
|
+
Parameters
|
|
788
|
+
----------
|
|
789
|
+
violations : list[InvariantViolation]
|
|
790
|
+
List of violations to report.
|
|
791
|
+
|
|
792
|
+
Returns
|
|
793
|
+
-------
|
|
794
|
+
str
|
|
795
|
+
Human-readable report.
|
|
796
|
+
"""
|
|
797
|
+
if not violations:
|
|
798
|
+
return "✅ No TNFR invariant violations found."
|
|
799
|
+
|
|
800
|
+
report_lines = ["\n🚨 TNFR Invariant Violations Detected:\n"]
|
|
801
|
+
|
|
802
|
+
# Agrupar por severidad
|
|
803
|
+
by_severity: dict[InvariantSeverity, list[InvariantViolation]] = {}
|
|
804
|
+
for v in violations:
|
|
805
|
+
if v.severity not in by_severity:
|
|
806
|
+
by_severity[v.severity] = []
|
|
807
|
+
by_severity[v.severity].append(v)
|
|
808
|
+
|
|
809
|
+
# Reporte por severidad
|
|
810
|
+
severity_icons = {
|
|
811
|
+
InvariantSeverity.INFO: "ℹ️",
|
|
812
|
+
InvariantSeverity.WARNING: "⚠️",
|
|
813
|
+
InvariantSeverity.ERROR: "❌",
|
|
814
|
+
InvariantSeverity.CRITICAL: "💥",
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
for severity in [
|
|
818
|
+
InvariantSeverity.CRITICAL,
|
|
819
|
+
InvariantSeverity.ERROR,
|
|
820
|
+
InvariantSeverity.WARNING,
|
|
821
|
+
InvariantSeverity.INFO,
|
|
822
|
+
]:
|
|
823
|
+
if severity in by_severity:
|
|
824
|
+
report_lines.append(
|
|
825
|
+
f"\n{severity_icons[severity]} {severity.value.upper()} "
|
|
826
|
+
f"({len(by_severity[severity])}):\n"
|
|
827
|
+
)
|
|
828
|
+
|
|
829
|
+
for violation in by_severity[severity]:
|
|
830
|
+
report_lines.append(
|
|
831
|
+
f" Invariant #{violation.invariant_id}: {violation.description}"
|
|
832
|
+
)
|
|
833
|
+
if violation.node_id:
|
|
834
|
+
report_lines.append(f" Node: {violation.node_id}")
|
|
835
|
+
if violation.expected_value and violation.actual_value:
|
|
836
|
+
report_lines.append(f" Expected: {violation.expected_value}")
|
|
837
|
+
report_lines.append(f" Actual: {violation.actual_value}")
|
|
838
|
+
if violation.suggestion:
|
|
839
|
+
report_lines.append(
|
|
840
|
+
f" 💡 Suggestion: {violation.suggestion}"
|
|
841
|
+
)
|
|
842
|
+
report_lines.append("")
|
|
843
|
+
|
|
844
|
+
return "\n".join(report_lines)
|
|
845
|
+
|
|
846
|
+
def export_to_json(self, violations: list[InvariantViolation]) -> str:
|
|
847
|
+
"""Export violations to JSON format.
|
|
848
|
+
|
|
849
|
+
Parameters
|
|
850
|
+
----------
|
|
851
|
+
violations : list[InvariantViolation]
|
|
852
|
+
List of violations to export.
|
|
853
|
+
|
|
854
|
+
Returns
|
|
855
|
+
-------
|
|
856
|
+
str
|
|
857
|
+
JSON-formatted string of violations.
|
|
858
|
+
"""
|
|
859
|
+
import json
|
|
860
|
+
|
|
861
|
+
violations_data = []
|
|
862
|
+
for v in violations:
|
|
863
|
+
violations_data.append(
|
|
864
|
+
{
|
|
865
|
+
"invariant_id": v.invariant_id,
|
|
866
|
+
"severity": v.severity.value,
|
|
867
|
+
"description": v.description,
|
|
868
|
+
"node_id": v.node_id,
|
|
869
|
+
"expected_value": (
|
|
870
|
+
str(v.expected_value) if v.expected_value else None
|
|
871
|
+
),
|
|
872
|
+
"actual_value": str(v.actual_value) if v.actual_value else None,
|
|
873
|
+
"suggestion": v.suggestion,
|
|
874
|
+
}
|
|
875
|
+
)
|
|
876
|
+
|
|
877
|
+
return json.dumps(
|
|
878
|
+
{
|
|
879
|
+
"total_violations": len(violations),
|
|
880
|
+
"by_severity": {
|
|
881
|
+
InvariantSeverity.CRITICAL.value: len(
|
|
882
|
+
[
|
|
883
|
+
v
|
|
884
|
+
for v in violations
|
|
885
|
+
if v.severity == InvariantSeverity.CRITICAL
|
|
886
|
+
]
|
|
887
|
+
),
|
|
888
|
+
InvariantSeverity.ERROR.value: len(
|
|
889
|
+
[v for v in violations if v.severity == InvariantSeverity.ERROR]
|
|
890
|
+
),
|
|
891
|
+
InvariantSeverity.WARNING.value: len(
|
|
892
|
+
[
|
|
893
|
+
v
|
|
894
|
+
for v in violations
|
|
895
|
+
if v.severity == InvariantSeverity.WARNING
|
|
896
|
+
]
|
|
897
|
+
),
|
|
898
|
+
InvariantSeverity.INFO.value: len(
|
|
899
|
+
[v for v in violations if v.severity == InvariantSeverity.INFO]
|
|
900
|
+
),
|
|
901
|
+
},
|
|
902
|
+
"violations": violations_data,
|
|
903
|
+
},
|
|
904
|
+
indent=2,
|
|
905
|
+
)
|
|
906
|
+
|
|
907
|
+
def export_to_html(self, violations: list[InvariantViolation]) -> str:
|
|
908
|
+
"""Export violations to HTML format.
|
|
909
|
+
|
|
910
|
+
Parameters
|
|
911
|
+
----------
|
|
912
|
+
violations : list[InvariantViolation]
|
|
913
|
+
List of violations to export.
|
|
914
|
+
|
|
915
|
+
Returns
|
|
916
|
+
-------
|
|
917
|
+
str
|
|
918
|
+
HTML-formatted string of violations.
|
|
919
|
+
"""
|
|
920
|
+
if not violations:
|
|
921
|
+
return """
|
|
922
|
+
<!DOCTYPE html>
|
|
923
|
+
<html>
|
|
924
|
+
<head>
|
|
925
|
+
<title>TNFR Validation Report</title>
|
|
926
|
+
<style>
|
|
927
|
+
body { font-family: Arial, sans-serif; margin: 40px; }
|
|
928
|
+
.success { color: green; font-size: 24px; }
|
|
929
|
+
</style>
|
|
930
|
+
</head>
|
|
931
|
+
<body>
|
|
932
|
+
<h1>TNFR Validation Report</h1>
|
|
933
|
+
<p class="success">✅ No TNFR invariant violations found.</p>
|
|
934
|
+
</body>
|
|
935
|
+
</html>
|
|
936
|
+
"""
|
|
937
|
+
|
|
938
|
+
# Group by severity
|
|
939
|
+
by_severity: dict[InvariantSeverity, list[InvariantViolation]] = {}
|
|
940
|
+
for v in violations:
|
|
941
|
+
if v.severity not in by_severity:
|
|
942
|
+
by_severity[v.severity] = []
|
|
943
|
+
by_severity[v.severity].append(v)
|
|
944
|
+
|
|
945
|
+
severity_colors = {
|
|
946
|
+
InvariantSeverity.INFO: "#17a2b8",
|
|
947
|
+
InvariantSeverity.WARNING: "#ffc107",
|
|
948
|
+
InvariantSeverity.ERROR: "#dc3545",
|
|
949
|
+
InvariantSeverity.CRITICAL: "#6f42c1",
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
html_parts = [
|
|
953
|
+
"""
|
|
954
|
+
<!DOCTYPE html>
|
|
955
|
+
<html>
|
|
956
|
+
<head>
|
|
957
|
+
<title>TNFR Validation Report</title>
|
|
958
|
+
<style>
|
|
959
|
+
body {{ font-family: Arial, sans-serif; margin: 40px; background-color: #f5f5f5; }}
|
|
960
|
+
h1 {{ color: #333; }}
|
|
961
|
+
.summary {{ background: white; padding: 20px; border-radius: 5px; margin-bottom: 20px; }}
|
|
962
|
+
.severity-section {{ background: white; padding: 20px; border-radius: 5px; margin-bottom: 20px; }}
|
|
963
|
+
.severity-header {{ font-size: 20px; font-weight: bold; margin-bottom: 15px; }}
|
|
964
|
+
.violation {{ background: #f9f9f9; padding: 15px; margin-bottom: 10px; border-left: 4px solid; border-radius: 3px; }}
|
|
965
|
+
.violation-title {{ font-weight: bold; margin-bottom: 5px; }}
|
|
966
|
+
.violation-detail {{ margin-left: 20px; color: #666; }}
|
|
967
|
+
.suggestion {{ background: #e7f5ff; padding: 10px; margin-top: 10px; border-radius: 3px; }}
|
|
968
|
+
</style>
|
|
969
|
+
</head>
|
|
970
|
+
<body>
|
|
971
|
+
<h1>🚨 TNFR Validation Report</h1>
|
|
972
|
+
<div class="summary">
|
|
973
|
+
<h2>Summary</h2>
|
|
974
|
+
<p><strong>Total Violations:</strong> {{}}</p>
|
|
975
|
+
""".format(
|
|
976
|
+
len(violations)
|
|
977
|
+
)
|
|
978
|
+
]
|
|
979
|
+
|
|
980
|
+
for severity in [
|
|
981
|
+
InvariantSeverity.CRITICAL,
|
|
982
|
+
InvariantSeverity.ERROR,
|
|
983
|
+
InvariantSeverity.WARNING,
|
|
984
|
+
InvariantSeverity.INFO,
|
|
985
|
+
]:
|
|
986
|
+
count = len(by_severity.get(severity, []))
|
|
987
|
+
if count > 0:
|
|
988
|
+
html_parts.append(
|
|
989
|
+
f"<p><strong>{severity.value.upper()}:</strong> {count}</p>"
|
|
990
|
+
)
|
|
991
|
+
|
|
992
|
+
html_parts.append("</div>")
|
|
993
|
+
|
|
994
|
+
for severity in [
|
|
995
|
+
InvariantSeverity.CRITICAL,
|
|
996
|
+
InvariantSeverity.ERROR,
|
|
997
|
+
InvariantSeverity.WARNING,
|
|
998
|
+
InvariantSeverity.INFO,
|
|
999
|
+
]:
|
|
1000
|
+
if severity in by_severity:
|
|
1001
|
+
color = severity_colors[severity]
|
|
1002
|
+
html_parts.append(
|
|
1003
|
+
f"""
|
|
1004
|
+
<div class="severity-section">
|
|
1005
|
+
<div class="severity-header" style="color: {color};">
|
|
1006
|
+
{severity.value.upper()} ({len(by_severity[severity])})
|
|
1007
|
+
</div>
|
|
1008
|
+
"""
|
|
1009
|
+
)
|
|
1010
|
+
|
|
1011
|
+
for violation in by_severity[severity]:
|
|
1012
|
+
html_parts.append(
|
|
1013
|
+
f"""
|
|
1014
|
+
<div class="violation" style="border-left-color: {color};">
|
|
1015
|
+
<div class="violation-title">
|
|
1016
|
+
Invariant #{violation.invariant_id}: {violation.description}
|
|
1017
|
+
</div>
|
|
1018
|
+
"""
|
|
1019
|
+
)
|
|
1020
|
+
|
|
1021
|
+
if violation.node_id:
|
|
1022
|
+
html_parts.append(
|
|
1023
|
+
f'<div class="violation-detail"><strong>Node:</strong> {violation.node_id}</div>'
|
|
1024
|
+
)
|
|
1025
|
+
|
|
1026
|
+
if violation.expected_value and violation.actual_value:
|
|
1027
|
+
html_parts.append(
|
|
1028
|
+
f'<div class="violation-detail"><strong>Expected:</strong> {violation.expected_value}</div>'
|
|
1029
|
+
)
|
|
1030
|
+
html_parts.append(
|
|
1031
|
+
f'<div class="violation-detail"><strong>Actual:</strong> {violation.actual_value}</div>'
|
|
1032
|
+
)
|
|
1033
|
+
|
|
1034
|
+
if violation.suggestion:
|
|
1035
|
+
html_parts.append(
|
|
1036
|
+
f'<div class="suggestion">💡 <strong>Suggestion:</strong> {violation.suggestion}</div>'
|
|
1037
|
+
)
|
|
1038
|
+
|
|
1039
|
+
html_parts.append("</div>")
|
|
1040
|
+
|
|
1041
|
+
html_parts.append("</div>")
|
|
1042
|
+
|
|
1043
|
+
html_parts.append(
|
|
1044
|
+
"""
|
|
1045
|
+
</body>
|
|
1046
|
+
</html>
|
|
1047
|
+
"""
|
|
1048
|
+
)
|
|
1049
|
+
|
|
1050
|
+
return "".join(html_parts)
|
|
1051
|
+
|
|
1052
|
+
|
|
1053
|
+
class TNFRValidationError(Exception):
|
|
1054
|
+
"""Exception raised when TNFR invariant violations are detected."""
|
|
1055
|
+
|
|
1056
|
+
def __init__(self, violations: list[InvariantViolation]) -> None:
|
|
1057
|
+
self.violations = violations
|
|
1058
|
+
validator = TNFRValidator()
|
|
1059
|
+
self.report = validator.generate_report(violations)
|
|
1060
|
+
super().__init__(self.report)
|
|
1061
|
+
|
|
1062
|
+
def export_to_json(self, violations: list[InvariantViolation]) -> str:
|
|
1063
|
+
"""Export violations to JSON format.
|
|
1064
|
+
|
|
1065
|
+
Parameters
|
|
1066
|
+
----------
|
|
1067
|
+
violations : list[InvariantViolation]
|
|
1068
|
+
List of violations to export.
|
|
1069
|
+
|
|
1070
|
+
Returns
|
|
1071
|
+
-------
|
|
1072
|
+
str
|
|
1073
|
+
JSON-formatted string of violations.
|
|
1074
|
+
"""
|
|
1075
|
+
import json
|
|
1076
|
+
|
|
1077
|
+
violations_data = []
|
|
1078
|
+
for v in violations:
|
|
1079
|
+
violations_data.append(
|
|
1080
|
+
{
|
|
1081
|
+
"invariant_id": v.invariant_id,
|
|
1082
|
+
"severity": v.severity.value,
|
|
1083
|
+
"description": v.description,
|
|
1084
|
+
"node_id": v.node_id,
|
|
1085
|
+
"expected_value": (
|
|
1086
|
+
str(v.expected_value) if v.expected_value else None
|
|
1087
|
+
),
|
|
1088
|
+
"actual_value": str(v.actual_value) if v.actual_value else None,
|
|
1089
|
+
"suggestion": v.suggestion,
|
|
1090
|
+
}
|
|
1091
|
+
)
|
|
1092
|
+
|
|
1093
|
+
return json.dumps(
|
|
1094
|
+
{
|
|
1095
|
+
"total_violations": len(violations),
|
|
1096
|
+
"by_severity": {
|
|
1097
|
+
InvariantSeverity.CRITICAL.value: len(
|
|
1098
|
+
[
|
|
1099
|
+
v
|
|
1100
|
+
for v in violations
|
|
1101
|
+
if v.severity == InvariantSeverity.CRITICAL
|
|
1102
|
+
]
|
|
1103
|
+
),
|
|
1104
|
+
InvariantSeverity.ERROR.value: len(
|
|
1105
|
+
[v for v in violations if v.severity == InvariantSeverity.ERROR]
|
|
1106
|
+
),
|
|
1107
|
+
InvariantSeverity.WARNING.value: len(
|
|
1108
|
+
[
|
|
1109
|
+
v
|
|
1110
|
+
for v in violations
|
|
1111
|
+
if v.severity == InvariantSeverity.WARNING
|
|
1112
|
+
]
|
|
1113
|
+
),
|
|
1114
|
+
InvariantSeverity.INFO.value: len(
|
|
1115
|
+
[v for v in violations if v.severity == InvariantSeverity.INFO]
|
|
1116
|
+
),
|
|
1117
|
+
},
|
|
1118
|
+
"violations": violations_data,
|
|
1119
|
+
},
|
|
1120
|
+
indent=2,
|
|
1121
|
+
)
|
|
1122
|
+
|
|
1123
|
+
def export_to_html(self, violations: list[InvariantViolation]) -> str:
|
|
1124
|
+
"""Export violations to HTML format.
|
|
1125
|
+
|
|
1126
|
+
Parameters
|
|
1127
|
+
----------
|
|
1128
|
+
violations : list[InvariantViolation]
|
|
1129
|
+
List of violations to export.
|
|
1130
|
+
|
|
1131
|
+
Returns
|
|
1132
|
+
-------
|
|
1133
|
+
str
|
|
1134
|
+
HTML-formatted string of violations.
|
|
1135
|
+
"""
|
|
1136
|
+
if not violations:
|
|
1137
|
+
return """
|
|
1138
|
+
<!DOCTYPE html>
|
|
1139
|
+
<html>
|
|
1140
|
+
<head>
|
|
1141
|
+
<title>TNFR Validation Report</title>
|
|
1142
|
+
<style>
|
|
1143
|
+
body { font-family: Arial, sans-serif; margin: 40px; }
|
|
1144
|
+
.success { color: green; font-size: 24px; }
|
|
1145
|
+
</style>
|
|
1146
|
+
</head>
|
|
1147
|
+
<body>
|
|
1148
|
+
<h1>TNFR Validation Report</h1>
|
|
1149
|
+
<p class="success">✅ No TNFR invariant violations found.</p>
|
|
1150
|
+
</body>
|
|
1151
|
+
</html>
|
|
1152
|
+
"""
|
|
1153
|
+
|
|
1154
|
+
# Group by severity
|
|
1155
|
+
by_severity: dict[InvariantSeverity, list[InvariantViolation]] = {}
|
|
1156
|
+
for v in violations:
|
|
1157
|
+
if v.severity not in by_severity:
|
|
1158
|
+
by_severity[v.severity] = []
|
|
1159
|
+
by_severity[v.severity].append(v)
|
|
1160
|
+
|
|
1161
|
+
severity_colors = {
|
|
1162
|
+
InvariantSeverity.INFO: "#17a2b8",
|
|
1163
|
+
InvariantSeverity.WARNING: "#ffc107",
|
|
1164
|
+
InvariantSeverity.ERROR: "#dc3545",
|
|
1165
|
+
InvariantSeverity.CRITICAL: "#6f42c1",
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
html_parts = [
|
|
1169
|
+
"""
|
|
1170
|
+
<!DOCTYPE html>
|
|
1171
|
+
<html>
|
|
1172
|
+
<head>
|
|
1173
|
+
<title>TNFR Validation Report</title>
|
|
1174
|
+
<style>
|
|
1175
|
+
body { font-family: Arial, sans-serif; margin: 40px; background-color: #f5f5f5; }
|
|
1176
|
+
h1 { color: #333; }
|
|
1177
|
+
.summary { background: white; padding: 20px; border-radius: 5px; margin-bottom: 20px; }
|
|
1178
|
+
.severity-section { background: white; padding: 20px; border-radius: 5px; margin-bottom: 20px; }
|
|
1179
|
+
.severity-header { font-size: 20px; font-weight: bold; margin-bottom: 15px; }
|
|
1180
|
+
.violation { background: #f9f9f9; padding: 15px; margin-bottom: 10px; border-left: 4px solid; border-radius: 3px; }
|
|
1181
|
+
.violation-title { font-weight: bold; margin-bottom: 5px; }
|
|
1182
|
+
.violation-detail { margin-left: 20px; color: #666; }
|
|
1183
|
+
.suggestion { background: #e7f5ff; padding: 10px; margin-top: 10px; border-radius: 3px; }
|
|
1184
|
+
</style>
|
|
1185
|
+
</head>
|
|
1186
|
+
<body>
|
|
1187
|
+
<h1>🚨 TNFR Validation Report</h1>
|
|
1188
|
+
<div class="summary">
|
|
1189
|
+
<h2>Summary</h2>
|
|
1190
|
+
<p><strong>Total Violations:</strong> {}</p>
|
|
1191
|
+
""".format(
|
|
1192
|
+
len(violations)
|
|
1193
|
+
)
|
|
1194
|
+
]
|
|
1195
|
+
|
|
1196
|
+
for severity in [
|
|
1197
|
+
InvariantSeverity.CRITICAL,
|
|
1198
|
+
InvariantSeverity.ERROR,
|
|
1199
|
+
InvariantSeverity.WARNING,
|
|
1200
|
+
InvariantSeverity.INFO,
|
|
1201
|
+
]:
|
|
1202
|
+
count = len(by_severity.get(severity, []))
|
|
1203
|
+
if count > 0:
|
|
1204
|
+
html_parts.append(
|
|
1205
|
+
f"<p><strong>{severity.value.upper()}:</strong> {count}</p>"
|
|
1206
|
+
)
|
|
1207
|
+
|
|
1208
|
+
html_parts.append("</div>")
|
|
1209
|
+
|
|
1210
|
+
for severity in [
|
|
1211
|
+
InvariantSeverity.CRITICAL,
|
|
1212
|
+
InvariantSeverity.ERROR,
|
|
1213
|
+
InvariantSeverity.WARNING,
|
|
1214
|
+
InvariantSeverity.INFO,
|
|
1215
|
+
]:
|
|
1216
|
+
if severity in by_severity:
|
|
1217
|
+
color = severity_colors[severity]
|
|
1218
|
+
html_parts.append(
|
|
1219
|
+
f"""
|
|
1220
|
+
<div class="severity-section">
|
|
1221
|
+
<div class="severity-header" style="color: {color};">
|
|
1222
|
+
{severity.value.upper()} ({len(by_severity[severity])})
|
|
1223
|
+
</div>
|
|
1224
|
+
"""
|
|
1225
|
+
)
|
|
1226
|
+
|
|
1227
|
+
for violation in by_severity[severity]:
|
|
1228
|
+
html_parts.append(
|
|
1229
|
+
f"""
|
|
1230
|
+
<div class="violation" style="border-left-color: {color};">
|
|
1231
|
+
<div class="violation-title">
|
|
1232
|
+
Invariant #{violation.invariant_id}: {violation.description}
|
|
1233
|
+
</div>
|
|
1234
|
+
"""
|
|
1235
|
+
)
|
|
1236
|
+
|
|
1237
|
+
if violation.node_id:
|
|
1238
|
+
html_parts.append(
|
|
1239
|
+
f'<div class="violation-detail"><strong>Node:</strong> {violation.node_id}</div>'
|
|
1240
|
+
)
|
|
1241
|
+
|
|
1242
|
+
if violation.expected_value and violation.actual_value:
|
|
1243
|
+
html_parts.append(
|
|
1244
|
+
f'<div class="violation-detail"><strong>Expected:</strong> {violation.expected_value}</div>'
|
|
1245
|
+
)
|
|
1246
|
+
html_parts.append(
|
|
1247
|
+
f'<div class="violation-detail"><strong>Actual:</strong> {violation.actual_value}</div>'
|
|
1248
|
+
)
|
|
1249
|
+
|
|
1250
|
+
if violation.suggestion:
|
|
1251
|
+
html_parts.append(
|
|
1252
|
+
f'<div class="suggestion">💡 <strong>Suggestion:</strong> {violation.suggestion}</div>'
|
|
1253
|
+
)
|
|
1254
|
+
|
|
1255
|
+
html_parts.append("</div>")
|
|
1256
|
+
|
|
1257
|
+
html_parts.append("</div>")
|
|
1258
|
+
|
|
1259
|
+
html_parts.append(
|
|
1260
|
+
"""
|
|
1261
|
+
</body>
|
|
1262
|
+
</html>
|
|
1263
|
+
"""
|
|
1264
|
+
)
|
|
1265
|
+
|
|
1266
|
+
return "".join(html_parts)
|