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.

Files changed (365) hide show
  1. tnfr/__init__.py +334 -50
  2. tnfr/__init__.pyi +33 -0
  3. tnfr/_compat.py +10 -0
  4. tnfr/_generated_version.py +34 -0
  5. tnfr/_version.py +49 -0
  6. tnfr/_version.pyi +7 -0
  7. tnfr/alias.py +214 -37
  8. tnfr/alias.pyi +108 -0
  9. tnfr/backends/__init__.py +354 -0
  10. tnfr/backends/jax_backend.py +173 -0
  11. tnfr/backends/numpy_backend.py +238 -0
  12. tnfr/backends/optimized_numpy.py +420 -0
  13. tnfr/backends/torch_backend.py +408 -0
  14. tnfr/cache.py +149 -556
  15. tnfr/cache.pyi +13 -0
  16. tnfr/cli/__init__.py +51 -16
  17. tnfr/cli/__init__.pyi +26 -0
  18. tnfr/cli/arguments.py +344 -32
  19. tnfr/cli/arguments.pyi +29 -0
  20. tnfr/cli/execution.py +676 -50
  21. tnfr/cli/execution.pyi +70 -0
  22. tnfr/cli/interactive_validator.py +614 -0
  23. tnfr/cli/utils.py +18 -3
  24. tnfr/cli/utils.pyi +7 -0
  25. tnfr/cli/validate.py +236 -0
  26. tnfr/compat/__init__.py +85 -0
  27. tnfr/compat/dataclass.py +136 -0
  28. tnfr/compat/jsonschema_stub.py +61 -0
  29. tnfr/compat/matplotlib_stub.py +73 -0
  30. tnfr/compat/numpy_stub.py +155 -0
  31. tnfr/config/__init__.py +224 -0
  32. tnfr/config/__init__.pyi +10 -0
  33. tnfr/{constants_glyphs.py → config/constants.py} +26 -20
  34. tnfr/config/constants.pyi +12 -0
  35. tnfr/config/defaults.py +54 -0
  36. tnfr/{constants/core.py → config/defaults_core.py} +59 -6
  37. tnfr/config/defaults_init.py +33 -0
  38. tnfr/config/defaults_metric.py +104 -0
  39. tnfr/config/feature_flags.py +81 -0
  40. tnfr/config/feature_flags.pyi +16 -0
  41. tnfr/config/glyph_constants.py +31 -0
  42. tnfr/config/init.py +77 -0
  43. tnfr/config/init.pyi +8 -0
  44. tnfr/config/operator_names.py +254 -0
  45. tnfr/config/operator_names.pyi +36 -0
  46. tnfr/config/physics_derivation.py +354 -0
  47. tnfr/config/presets.py +83 -0
  48. tnfr/config/presets.pyi +7 -0
  49. tnfr/config/security.py +927 -0
  50. tnfr/config/thresholds.py +114 -0
  51. tnfr/config/tnfr_config.py +498 -0
  52. tnfr/constants/__init__.py +51 -133
  53. tnfr/constants/__init__.pyi +92 -0
  54. tnfr/constants/aliases.py +33 -0
  55. tnfr/constants/aliases.pyi +27 -0
  56. tnfr/constants/init.py +3 -1
  57. tnfr/constants/init.pyi +12 -0
  58. tnfr/constants/metric.py +9 -15
  59. tnfr/constants/metric.pyi +19 -0
  60. tnfr/core/__init__.py +33 -0
  61. tnfr/core/container.py +226 -0
  62. tnfr/core/default_implementations.py +329 -0
  63. tnfr/core/interfaces.py +279 -0
  64. tnfr/dynamics/__init__.py +213 -633
  65. tnfr/dynamics/__init__.pyi +83 -0
  66. tnfr/dynamics/adaptation.py +267 -0
  67. tnfr/dynamics/adaptation.pyi +7 -0
  68. tnfr/dynamics/adaptive_sequences.py +189 -0
  69. tnfr/dynamics/adaptive_sequences.pyi +14 -0
  70. tnfr/dynamics/aliases.py +23 -0
  71. tnfr/dynamics/aliases.pyi +19 -0
  72. tnfr/dynamics/bifurcation.py +232 -0
  73. tnfr/dynamics/canonical.py +229 -0
  74. tnfr/dynamics/canonical.pyi +48 -0
  75. tnfr/dynamics/coordination.py +385 -0
  76. tnfr/dynamics/coordination.pyi +25 -0
  77. tnfr/dynamics/dnfr.py +2699 -398
  78. tnfr/dynamics/dnfr.pyi +26 -0
  79. tnfr/dynamics/dynamic_limits.py +225 -0
  80. tnfr/dynamics/feedback.py +252 -0
  81. tnfr/dynamics/feedback.pyi +24 -0
  82. tnfr/dynamics/fused_dnfr.py +454 -0
  83. tnfr/dynamics/homeostasis.py +157 -0
  84. tnfr/dynamics/homeostasis.pyi +14 -0
  85. tnfr/dynamics/integrators.py +496 -102
  86. tnfr/dynamics/integrators.pyi +36 -0
  87. tnfr/dynamics/learning.py +310 -0
  88. tnfr/dynamics/learning.pyi +33 -0
  89. tnfr/dynamics/metabolism.py +254 -0
  90. tnfr/dynamics/nbody.py +796 -0
  91. tnfr/dynamics/nbody_tnfr.py +783 -0
  92. tnfr/dynamics/propagation.py +326 -0
  93. tnfr/dynamics/runtime.py +908 -0
  94. tnfr/dynamics/runtime.pyi +77 -0
  95. tnfr/dynamics/sampling.py +10 -5
  96. tnfr/dynamics/sampling.pyi +7 -0
  97. tnfr/dynamics/selectors.py +711 -0
  98. tnfr/dynamics/selectors.pyi +85 -0
  99. tnfr/dynamics/structural_clip.py +207 -0
  100. tnfr/errors/__init__.py +37 -0
  101. tnfr/errors/contextual.py +492 -0
  102. tnfr/execution.py +77 -55
  103. tnfr/execution.pyi +45 -0
  104. tnfr/extensions/__init__.py +205 -0
  105. tnfr/extensions/__init__.pyi +18 -0
  106. tnfr/extensions/base.py +173 -0
  107. tnfr/extensions/base.pyi +35 -0
  108. tnfr/extensions/business/__init__.py +71 -0
  109. tnfr/extensions/business/__init__.pyi +11 -0
  110. tnfr/extensions/business/cookbook.py +88 -0
  111. tnfr/extensions/business/cookbook.pyi +8 -0
  112. tnfr/extensions/business/health_analyzers.py +202 -0
  113. tnfr/extensions/business/health_analyzers.pyi +9 -0
  114. tnfr/extensions/business/patterns.py +183 -0
  115. tnfr/extensions/business/patterns.pyi +8 -0
  116. tnfr/extensions/medical/__init__.py +73 -0
  117. tnfr/extensions/medical/__init__.pyi +11 -0
  118. tnfr/extensions/medical/cookbook.py +88 -0
  119. tnfr/extensions/medical/cookbook.pyi +8 -0
  120. tnfr/extensions/medical/health_analyzers.py +181 -0
  121. tnfr/extensions/medical/health_analyzers.pyi +9 -0
  122. tnfr/extensions/medical/patterns.py +163 -0
  123. tnfr/extensions/medical/patterns.pyi +8 -0
  124. tnfr/flatten.py +29 -50
  125. tnfr/flatten.pyi +21 -0
  126. tnfr/gamma.py +66 -53
  127. tnfr/gamma.pyi +36 -0
  128. tnfr/glyph_history.py +144 -57
  129. tnfr/glyph_history.pyi +35 -0
  130. tnfr/glyph_runtime.py +19 -0
  131. tnfr/glyph_runtime.pyi +8 -0
  132. tnfr/immutable.py +70 -30
  133. tnfr/immutable.pyi +36 -0
  134. tnfr/initialization.py +22 -16
  135. tnfr/initialization.pyi +65 -0
  136. tnfr/io.py +5 -241
  137. tnfr/io.pyi +13 -0
  138. tnfr/locking.pyi +7 -0
  139. tnfr/mathematics/__init__.py +79 -0
  140. tnfr/mathematics/backend.py +453 -0
  141. tnfr/mathematics/backend.pyi +99 -0
  142. tnfr/mathematics/dynamics.py +408 -0
  143. tnfr/mathematics/dynamics.pyi +90 -0
  144. tnfr/mathematics/epi.py +391 -0
  145. tnfr/mathematics/epi.pyi +65 -0
  146. tnfr/mathematics/generators.py +242 -0
  147. tnfr/mathematics/generators.pyi +29 -0
  148. tnfr/mathematics/metrics.py +119 -0
  149. tnfr/mathematics/metrics.pyi +16 -0
  150. tnfr/mathematics/operators.py +239 -0
  151. tnfr/mathematics/operators.pyi +59 -0
  152. tnfr/mathematics/operators_factory.py +124 -0
  153. tnfr/mathematics/operators_factory.pyi +11 -0
  154. tnfr/mathematics/projection.py +87 -0
  155. tnfr/mathematics/projection.pyi +33 -0
  156. tnfr/mathematics/runtime.py +182 -0
  157. tnfr/mathematics/runtime.pyi +64 -0
  158. tnfr/mathematics/spaces.py +256 -0
  159. tnfr/mathematics/spaces.pyi +83 -0
  160. tnfr/mathematics/transforms.py +305 -0
  161. tnfr/mathematics/transforms.pyi +62 -0
  162. tnfr/metrics/__init__.py +47 -9
  163. tnfr/metrics/__init__.pyi +20 -0
  164. tnfr/metrics/buffer_cache.py +163 -0
  165. tnfr/metrics/buffer_cache.pyi +24 -0
  166. tnfr/metrics/cache_utils.py +214 -0
  167. tnfr/metrics/coherence.py +1510 -330
  168. tnfr/metrics/coherence.pyi +129 -0
  169. tnfr/metrics/common.py +23 -16
  170. tnfr/metrics/common.pyi +35 -0
  171. tnfr/metrics/core.py +251 -36
  172. tnfr/metrics/core.pyi +13 -0
  173. tnfr/metrics/diagnosis.py +709 -110
  174. tnfr/metrics/diagnosis.pyi +86 -0
  175. tnfr/metrics/emergence.py +245 -0
  176. tnfr/metrics/export.py +60 -18
  177. tnfr/metrics/export.pyi +7 -0
  178. tnfr/metrics/glyph_timing.py +233 -43
  179. tnfr/metrics/glyph_timing.pyi +81 -0
  180. tnfr/metrics/learning_metrics.py +280 -0
  181. tnfr/metrics/learning_metrics.pyi +21 -0
  182. tnfr/metrics/phase_coherence.py +351 -0
  183. tnfr/metrics/phase_compatibility.py +349 -0
  184. tnfr/metrics/reporting.py +63 -28
  185. tnfr/metrics/reporting.pyi +25 -0
  186. tnfr/metrics/sense_index.py +1126 -43
  187. tnfr/metrics/sense_index.pyi +9 -0
  188. tnfr/metrics/trig.py +215 -23
  189. tnfr/metrics/trig.pyi +13 -0
  190. tnfr/metrics/trig_cache.py +148 -24
  191. tnfr/metrics/trig_cache.pyi +10 -0
  192. tnfr/multiscale/__init__.py +32 -0
  193. tnfr/multiscale/hierarchical.py +517 -0
  194. tnfr/node.py +646 -140
  195. tnfr/node.pyi +139 -0
  196. tnfr/observers.py +160 -45
  197. tnfr/observers.pyi +31 -0
  198. tnfr/ontosim.py +23 -19
  199. tnfr/ontosim.pyi +28 -0
  200. tnfr/operators/__init__.py +1358 -106
  201. tnfr/operators/__init__.pyi +31 -0
  202. tnfr/operators/algebra.py +277 -0
  203. tnfr/operators/canonical_patterns.py +420 -0
  204. tnfr/operators/cascade.py +267 -0
  205. tnfr/operators/cycle_detection.py +358 -0
  206. tnfr/operators/definitions.py +4108 -0
  207. tnfr/operators/definitions.pyi +78 -0
  208. tnfr/operators/grammar.py +1164 -0
  209. tnfr/operators/grammar.pyi +140 -0
  210. tnfr/operators/hamiltonian.py +710 -0
  211. tnfr/operators/health_analyzer.py +809 -0
  212. tnfr/operators/jitter.py +107 -38
  213. tnfr/operators/jitter.pyi +11 -0
  214. tnfr/operators/lifecycle.py +314 -0
  215. tnfr/operators/metabolism.py +618 -0
  216. tnfr/operators/metrics.py +2138 -0
  217. tnfr/operators/network_analysis/__init__.py +27 -0
  218. tnfr/operators/network_analysis/source_detection.py +186 -0
  219. tnfr/operators/nodal_equation.py +395 -0
  220. tnfr/operators/pattern_detection.py +660 -0
  221. tnfr/operators/patterns.py +669 -0
  222. tnfr/operators/postconditions/__init__.py +38 -0
  223. tnfr/operators/postconditions/mutation.py +236 -0
  224. tnfr/operators/preconditions/__init__.py +1226 -0
  225. tnfr/operators/preconditions/coherence.py +305 -0
  226. tnfr/operators/preconditions/dissonance.py +236 -0
  227. tnfr/operators/preconditions/emission.py +128 -0
  228. tnfr/operators/preconditions/mutation.py +580 -0
  229. tnfr/operators/preconditions/reception.py +125 -0
  230. tnfr/operators/preconditions/resonance.py +364 -0
  231. tnfr/operators/registry.py +74 -0
  232. tnfr/operators/registry.pyi +9 -0
  233. tnfr/operators/remesh.py +1415 -91
  234. tnfr/operators/remesh.pyi +26 -0
  235. tnfr/operators/structural_units.py +268 -0
  236. tnfr/operators/unified_grammar.py +105 -0
  237. tnfr/parallel/__init__.py +54 -0
  238. tnfr/parallel/auto_scaler.py +234 -0
  239. tnfr/parallel/distributed.py +384 -0
  240. tnfr/parallel/engine.py +238 -0
  241. tnfr/parallel/gpu_engine.py +420 -0
  242. tnfr/parallel/monitoring.py +248 -0
  243. tnfr/parallel/partitioner.py +459 -0
  244. tnfr/py.typed +0 -0
  245. tnfr/recipes/__init__.py +22 -0
  246. tnfr/recipes/cookbook.py +743 -0
  247. tnfr/rng.py +75 -151
  248. tnfr/rng.pyi +26 -0
  249. tnfr/schemas/__init__.py +8 -0
  250. tnfr/schemas/grammar.json +94 -0
  251. tnfr/sdk/__init__.py +107 -0
  252. tnfr/sdk/__init__.pyi +19 -0
  253. tnfr/sdk/adaptive_system.py +173 -0
  254. tnfr/sdk/adaptive_system.pyi +21 -0
  255. tnfr/sdk/builders.py +370 -0
  256. tnfr/sdk/builders.pyi +51 -0
  257. tnfr/sdk/fluent.py +1121 -0
  258. tnfr/sdk/fluent.pyi +74 -0
  259. tnfr/sdk/templates.py +342 -0
  260. tnfr/sdk/templates.pyi +41 -0
  261. tnfr/sdk/utils.py +341 -0
  262. tnfr/secure_config.py +46 -0
  263. tnfr/security/__init__.py +70 -0
  264. tnfr/security/database.py +514 -0
  265. tnfr/security/subprocess.py +503 -0
  266. tnfr/security/validation.py +290 -0
  267. tnfr/selector.py +59 -22
  268. tnfr/selector.pyi +19 -0
  269. tnfr/sense.py +92 -67
  270. tnfr/sense.pyi +23 -0
  271. tnfr/services/__init__.py +17 -0
  272. tnfr/services/orchestrator.py +325 -0
  273. tnfr/sparse/__init__.py +39 -0
  274. tnfr/sparse/representations.py +492 -0
  275. tnfr/structural.py +639 -263
  276. tnfr/structural.pyi +83 -0
  277. tnfr/telemetry/__init__.py +35 -0
  278. tnfr/telemetry/cache_metrics.py +226 -0
  279. tnfr/telemetry/cache_metrics.pyi +64 -0
  280. tnfr/telemetry/nu_f.py +422 -0
  281. tnfr/telemetry/nu_f.pyi +108 -0
  282. tnfr/telemetry/verbosity.py +36 -0
  283. tnfr/telemetry/verbosity.pyi +15 -0
  284. tnfr/tokens.py +2 -4
  285. tnfr/tokens.pyi +36 -0
  286. tnfr/tools/__init__.py +20 -0
  287. tnfr/tools/domain_templates.py +478 -0
  288. tnfr/tools/sequence_generator.py +846 -0
  289. tnfr/topology/__init__.py +13 -0
  290. tnfr/topology/asymmetry.py +151 -0
  291. tnfr/trace.py +300 -126
  292. tnfr/trace.pyi +42 -0
  293. tnfr/tutorials/__init__.py +38 -0
  294. tnfr/tutorials/autonomous_evolution.py +285 -0
  295. tnfr/tutorials/interactive.py +1576 -0
  296. tnfr/tutorials/structural_metabolism.py +238 -0
  297. tnfr/types.py +743 -12
  298. tnfr/types.pyi +357 -0
  299. tnfr/units.py +68 -0
  300. tnfr/units.pyi +13 -0
  301. tnfr/utils/__init__.py +282 -0
  302. tnfr/utils/__init__.pyi +215 -0
  303. tnfr/utils/cache.py +4223 -0
  304. tnfr/utils/cache.pyi +470 -0
  305. tnfr/{callback_utils.py → utils/callbacks.py} +26 -39
  306. tnfr/utils/callbacks.pyi +49 -0
  307. tnfr/utils/chunks.py +108 -0
  308. tnfr/utils/chunks.pyi +22 -0
  309. tnfr/utils/data.py +428 -0
  310. tnfr/utils/data.pyi +74 -0
  311. tnfr/utils/graph.py +85 -0
  312. tnfr/utils/graph.pyi +10 -0
  313. tnfr/utils/init.py +821 -0
  314. tnfr/utils/init.pyi +80 -0
  315. tnfr/utils/io.py +559 -0
  316. tnfr/utils/io.pyi +66 -0
  317. tnfr/{helpers → utils}/numeric.py +51 -24
  318. tnfr/utils/numeric.pyi +21 -0
  319. tnfr/validation/__init__.py +257 -0
  320. tnfr/validation/__init__.pyi +85 -0
  321. tnfr/validation/compatibility.py +460 -0
  322. tnfr/validation/compatibility.pyi +6 -0
  323. tnfr/validation/config.py +73 -0
  324. tnfr/validation/graph.py +139 -0
  325. tnfr/validation/graph.pyi +18 -0
  326. tnfr/validation/input_validation.py +755 -0
  327. tnfr/validation/invariants.py +712 -0
  328. tnfr/validation/rules.py +253 -0
  329. tnfr/validation/rules.pyi +44 -0
  330. tnfr/validation/runtime.py +279 -0
  331. tnfr/validation/runtime.pyi +28 -0
  332. tnfr/validation/sequence_validator.py +162 -0
  333. tnfr/validation/soft_filters.py +170 -0
  334. tnfr/validation/soft_filters.pyi +32 -0
  335. tnfr/validation/spectral.py +164 -0
  336. tnfr/validation/spectral.pyi +42 -0
  337. tnfr/validation/validator.py +1266 -0
  338. tnfr/validation/window.py +39 -0
  339. tnfr/validation/window.pyi +1 -0
  340. tnfr/visualization/__init__.py +98 -0
  341. tnfr/visualization/cascade_viz.py +256 -0
  342. tnfr/visualization/hierarchy.py +284 -0
  343. tnfr/visualization/sequence_plotter.py +784 -0
  344. tnfr/viz/__init__.py +60 -0
  345. tnfr/viz/matplotlib.py +278 -0
  346. tnfr/viz/matplotlib.pyi +35 -0
  347. tnfr-8.5.0.dist-info/METADATA +573 -0
  348. tnfr-8.5.0.dist-info/RECORD +353 -0
  349. {tnfr-4.5.2.dist-info → tnfr-8.5.0.dist-info}/entry_points.txt +1 -0
  350. {tnfr-4.5.2.dist-info → tnfr-8.5.0.dist-info}/licenses/LICENSE.md +1 -1
  351. tnfr/collections_utils.py +0 -300
  352. tnfr/config.py +0 -32
  353. tnfr/grammar.py +0 -344
  354. tnfr/graph_utils.py +0 -84
  355. tnfr/helpers/__init__.py +0 -71
  356. tnfr/import_utils.py +0 -228
  357. tnfr/json_utils.py +0 -162
  358. tnfr/logging_utils.py +0 -116
  359. tnfr/presets.py +0 -60
  360. tnfr/validators.py +0 -84
  361. tnfr/value_utils.py +0 -59
  362. tnfr-4.5.2.dist-info/METADATA +0 -379
  363. tnfr-4.5.2.dist-info/RECORD +0 -67
  364. {tnfr-4.5.2.dist-info → tnfr-8.5.0.dist-info}/WHEEL +0 -0
  365. {tnfr-4.5.2.dist-info → tnfr-8.5.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1164 @@
1
+ """TNFR Canonical Grammar - Single Source of Truth.
2
+
3
+ This module implements the canonical TNFR grammar constraints that emerge
4
+ inevitably from TNFR physics.
5
+
6
+ All rules derive from the nodal equation ∂EPI/∂t = νf · ΔNFR(t), canonical
7
+ invariants, and formal contracts. No organizational conventions.
8
+
9
+ Canonical Constraints (U1-U4)
10
+ ------------------------------
11
+ U1: STRUCTURAL INITIATION & CLOSURE
12
+ U1a: Start with generators when needed
13
+ U1b: End with closure operators
14
+ Basis: ∂EPI/∂t undefined at EPI=0, sequences need coherent endpoints
15
+
16
+ U2: CONVERGENCE & BOUNDEDNESS
17
+ If destabilizers, then include stabilizers
18
+ Basis: ∫νf·ΔNFR dt must converge (integral convergence theorem)
19
+
20
+ U3: RESONANT COUPLING
21
+ If coupling/resonance, then verify phase compatibility
22
+ Basis: AGENTS.md Invariant #5 + resonance physics
23
+
24
+ U4: BIFURCATION DYNAMICS
25
+ U4a: If bifurcation triggers, then include handlers
26
+ U4b: If transformers, then recent destabilizer (+ prior IL for ZHIR)
27
+ Basis: Contract OZ + bifurcation theory
28
+
29
+ For complete derivations and physics basis, see UNIFIED_GRAMMAR_RULES.md
30
+
31
+ References
32
+ ----------
33
+ - UNIFIED_GRAMMAR_RULES.md: Complete physics derivations and mappings
34
+ - AGENTS.md: Canonical invariants and formal contracts
35
+ - TNFR.pdf: Nodal equation and bifurcation theory
36
+ """
37
+
38
+ from __future__ import annotations
39
+
40
+ from enum import Enum
41
+ from typing import TYPE_CHECKING, Any, List
42
+
43
+ if TYPE_CHECKING:
44
+ from ..types import NodeId, TNFRGraph, Glyph
45
+ from .definitions import Operator
46
+ else:
47
+ from ..types import Glyph
48
+
49
+
50
+ class StructuralPattern(Enum):
51
+ """Classification of structural patterns in TNFR sequences.
52
+
53
+ Used by canonical_patterns module for backward compatibility.
54
+ Deprecated - use pattern_detection module for new code.
55
+ """
56
+ BIFURCATED = "bifurcated"
57
+ THERAPEUTIC = "therapeutic"
58
+ EDUCATIONAL = "educational"
59
+ COMPLEX = "complex"
60
+ COMPRESS = "compress"
61
+ EXPLORE = "explore"
62
+ RESONATE = "resonate"
63
+
64
+
65
+ # ============================================================================
66
+ # Glyph-Function Name Mappings
67
+ # ============================================================================
68
+
69
+ # Mapping from Glyph to canonical function name
70
+ GLYPH_TO_FUNCTION = {
71
+ Glyph.AL: "emission",
72
+ Glyph.EN: "reception",
73
+ Glyph.IL: "coherence",
74
+ Glyph.OZ: "dissonance",
75
+ Glyph.UM: "coupling",
76
+ Glyph.RA: "resonance",
77
+ Glyph.SHA: "silence",
78
+ Glyph.VAL: "expansion",
79
+ Glyph.NUL: "contraction",
80
+ Glyph.THOL: "self_organization",
81
+ Glyph.ZHIR: "mutation",
82
+ Glyph.NAV: "transition",
83
+ Glyph.REMESH: "recursivity",
84
+ }
85
+
86
+ # Reverse mapping from function name to Glyph
87
+ FUNCTION_TO_GLYPH = {v: k for k, v in GLYPH_TO_FUNCTION.items()}
88
+
89
+
90
+ def glyph_function_name(
91
+ val: Any,
92
+ *,
93
+ default: Any = None,
94
+ ) -> Any:
95
+ """Convert glyph to canonical function name.
96
+
97
+ Parameters
98
+ ----------
99
+ val : Glyph | str | None
100
+ Glyph enum, glyph string value ('IL', 'OZ'), or function name to convert
101
+ default : str | None, optional
102
+ Default value if conversion fails
103
+
104
+ Returns
105
+ -------
106
+ str | None
107
+ Canonical function name or default
108
+
109
+ Notes
110
+ -----
111
+ Glyph enum inherits from str, so we must check for Enum type
112
+ BEFORE checking isinstance(val, str), otherwise Glyph instances
113
+ will be returned unchanged instead of being converted.
114
+
115
+ The function handles three input types:
116
+ 1. Glyph enum (e.g., Glyph.IL) → function name (e.g., 'coherence')
117
+ 2. Glyph string value (e.g., 'IL') → function name (e.g., 'coherence')
118
+ 3. Function name (e.g., 'coherence') → returned as-is
119
+ """
120
+ if val is None:
121
+ return default
122
+ # Check for Glyph/Enum BEFORE str (Glyph inherits from str)
123
+ if isinstance(val, Enum):
124
+ return GLYPH_TO_FUNCTION.get(val, default)
125
+ if isinstance(val, str):
126
+ # Check if it's a glyph string value ('IL', 'OZ', etc)
127
+ # Build reverse lookup on first use
128
+ if not hasattr(glyph_function_name, '_glyph_value_map'):
129
+ glyph_function_name._glyph_value_map = {
130
+ g.value: func for g, func in GLYPH_TO_FUNCTION.items()
131
+ }
132
+ # Try to convert glyph value to function name
133
+ func_name = glyph_function_name._glyph_value_map.get(val)
134
+ if func_name:
135
+ return func_name
136
+ # Otherwise assume it's already a function name
137
+ return val
138
+ return GLYPH_TO_FUNCTION.get(val, default)
139
+
140
+
141
+ def function_name_to_glyph(
142
+ val: Any,
143
+ *,
144
+ default: Any = None,
145
+ ) -> Any:
146
+ """Convert function name to glyph.
147
+
148
+ Parameters
149
+ ----------
150
+ val : str | Glyph | None
151
+ Function name or glyph to convert
152
+ default : Glyph | None, optional
153
+ Default value if conversion fails
154
+
155
+ Returns
156
+ -------
157
+ Glyph | None
158
+ Glyph or default
159
+ """
160
+ if val is None:
161
+ return default
162
+ if isinstance(val, Glyph):
163
+ return val
164
+ return FUNCTION_TO_GLYPH.get(val, default)
165
+
166
+
167
+ __all__ = [
168
+ "GrammarValidator",
169
+ "GrammarContext",
170
+ "validate_grammar",
171
+ "StructuralPattern",
172
+ # Error classes
173
+ "StructuralGrammarError",
174
+ "RepeatWindowError",
175
+ "MutationPreconditionError",
176
+ "TholClosureError",
177
+ "TransitionCompatibilityError",
178
+ "SequenceSyntaxError",
179
+ "GrammarConfigurationError",
180
+ "record_grammar_violation",
181
+ # Glyph mappings
182
+ "GLYPH_TO_FUNCTION",
183
+ "FUNCTION_TO_GLYPH",
184
+ "glyph_function_name",
185
+ "function_name_to_glyph",
186
+ # Grammar application functions
187
+ "apply_glyph_with_grammar",
188
+ "on_applied_glyph",
189
+ "enforce_canonical_grammar",
190
+ # Sequence validation
191
+ "validate_sequence",
192
+ "parse_sequence",
193
+ # Operator sets
194
+ "GENERATORS",
195
+ "CLOSURES",
196
+ "STABILIZERS",
197
+ "DESTABILIZERS",
198
+ "COUPLING_RESONANCE",
199
+ "BIFURCATION_TRIGGERS",
200
+ "BIFURCATION_HANDLERS",
201
+ "TRANSFORMERS",
202
+ ]
203
+
204
+
205
+ # ============================================================================
206
+ # Operator Sets (Derived from TNFR Physics)
207
+ # ============================================================================
208
+
209
+ # U1a: Generators - Create EPI from null/dormant states
210
+ GENERATORS = frozenset({"emission", "transition", "recursivity"})
211
+
212
+ # U1b: Closures - Leave system in coherent attractor states
213
+ CLOSURES = frozenset({"silence", "transition", "recursivity", "dissonance"})
214
+
215
+ # U2: Stabilizers - Provide negative feedback for convergence
216
+ STABILIZERS = frozenset({"coherence", "self_organization"})
217
+
218
+ # U2: Destabilizers - Increase |ΔNFR| (positive feedback)
219
+ DESTABILIZERS = frozenset({"dissonance", "mutation", "expansion"})
220
+
221
+ # U3: Coupling/Resonance - Require phase verification
222
+ COUPLING_RESONANCE = frozenset({"coupling", "resonance"})
223
+
224
+ # U4a: Bifurcation triggers - May initiate phase transitions
225
+ BIFURCATION_TRIGGERS = frozenset({"dissonance", "mutation"})
226
+
227
+ # U4a: Bifurcation handlers - Manage reorganization when ∂²EPI/∂t² > τ
228
+ BIFURCATION_HANDLERS = frozenset({"self_organization", "coherence"})
229
+
230
+ # U4b: Transformers - Execute structural bifurcations
231
+ TRANSFORMERS = frozenset({"mutation", "self_organization"})
232
+
233
+
234
+ # ============================================================================
235
+ # Grammar Errors
236
+ # ============================================================================
237
+
238
+
239
+ class StructuralGrammarError(RuntimeError):
240
+ """Base class for structural grammar violations.
241
+
242
+ Attributes
243
+ ----------
244
+ rule : str
245
+ Grammar rule that was violated
246
+ candidate : str
247
+ Operator/glyph that caused violation
248
+ message : str
249
+ Error description
250
+ window : int | None
251
+ Grammar window if applicable
252
+ threshold : float | None
253
+ Threshold value if applicable
254
+ order : Sequence[str] | None
255
+ Operator sequence if applicable
256
+ context : dict
257
+ Additional context information
258
+ """
259
+
260
+ def __init__(
261
+ self,
262
+ *,
263
+ rule: str,
264
+ candidate: str,
265
+ message: str,
266
+ window: int | None = None,
267
+ threshold: float | None = None,
268
+ order: list[str] | None = None,
269
+ context: dict[str, Any] | None = None,
270
+ ):
271
+ self.rule = rule
272
+ self.candidate = candidate
273
+ self.message = message
274
+ self.window = window
275
+ self.threshold = threshold
276
+ self.order = order
277
+ self.context = context or {}
278
+ super().__init__(message)
279
+
280
+ def attach_context(self, **context: Any) -> "StructuralGrammarError":
281
+ """Attach additional context to error.
282
+
283
+ Parameters
284
+ ----------
285
+ **context : Any
286
+ Additional context key-value pairs
287
+
288
+ Returns
289
+ -------
290
+ StructuralGrammarError
291
+ Self for chaining
292
+ """
293
+ self.context.update(context)
294
+ return self
295
+
296
+ def to_payload(self) -> dict[str, Any]:
297
+ """Convert error to dictionary payload.
298
+
299
+ Returns
300
+ -------
301
+ dict
302
+ Error information as dictionary
303
+ """
304
+ return {
305
+ "rule": self.rule,
306
+ "candidate": self.candidate,
307
+ "message": self.message,
308
+ "window": self.window,
309
+ "threshold": self.threshold,
310
+ "order": self.order,
311
+ "context": self.context,
312
+ }
313
+
314
+
315
+ class RepeatWindowError(StructuralGrammarError):
316
+ """Error for repeated operator within window."""
317
+ pass
318
+
319
+
320
+ class MutationPreconditionError(StructuralGrammarError):
321
+ """Error for mutation without proper preconditions."""
322
+ pass
323
+
324
+
325
+ class TholClosureError(StructuralGrammarError):
326
+ """Error for THOL without proper closure."""
327
+ pass
328
+
329
+
330
+ class TransitionCompatibilityError(StructuralGrammarError):
331
+ """Error for incompatible transition."""
332
+ pass
333
+
334
+
335
+ class SequenceSyntaxError(ValueError):
336
+ """Error in sequence syntax.
337
+
338
+ Attributes
339
+ ----------
340
+ index : int
341
+ Position in sequence where error occurred
342
+ token : object
343
+ Token that caused the error
344
+ message : str
345
+ Error description
346
+ """
347
+
348
+ def __init__(self, index: int, token: Any, message: str):
349
+ self.index = index
350
+ self.token = token
351
+ self.message = message
352
+ super().__init__(f"At index {index}, token '{token}': {message}")
353
+
354
+
355
+ class GrammarConfigurationError(ValueError):
356
+ """Error in grammar configuration.
357
+
358
+ Attributes
359
+ ----------
360
+ section : str
361
+ Configuration section with error
362
+ messages : list[str]
363
+ Error messages
364
+ details : list[tuple[str, str]]
365
+ Additional details
366
+ """
367
+
368
+ def __init__(
369
+ self,
370
+ section: str,
371
+ messages: list[str],
372
+ *,
373
+ details: list[tuple[str, str]] | None = None,
374
+ ):
375
+ self.section = section
376
+ self.messages = messages
377
+ self.details = details or []
378
+ msg = f"Configuration error in {section}: {'; '.join(messages)}"
379
+ super().__init__(msg)
380
+
381
+
382
+ def record_grammar_violation(
383
+ G: "TNFRGraph",
384
+ node: "NodeId",
385
+ error: StructuralGrammarError,
386
+ *,
387
+ stage: str,
388
+ ) -> None:
389
+ """Record grammar violation in node metadata.
390
+
391
+ Parameters
392
+ ----------
393
+ G : TNFRGraph
394
+ Graph containing node
395
+ node : NodeId
396
+ Node where violation occurred
397
+ error : StructuralGrammarError
398
+ Grammar error to record
399
+ stage : str
400
+ Processing stage when error occurred
401
+ """
402
+ if "grammar_violations" not in G.nodes[node]:
403
+ G.nodes[node]["grammar_violations"] = []
404
+ G.nodes[node]["grammar_violations"].append({
405
+ "stage": stage,
406
+ "error": error.to_payload(),
407
+ })
408
+
409
+
410
+ # ============================================================================
411
+ # Grammar Context
412
+ # ============================================================================
413
+
414
+
415
+ class GrammarContext:
416
+ """Context object for grammar validation.
417
+
418
+ Minimal implementation for import compatibility.
419
+
420
+ Attributes
421
+ ----------
422
+ G : TNFRGraph
423
+ Graph being validated
424
+ cfg_soft : dict
425
+ Soft configuration parameters
426
+ cfg_canon : dict
427
+ Canonical configuration parameters
428
+ norms : dict
429
+ Normalization parameters
430
+ """
431
+
432
+ def __init__(
433
+ self,
434
+ G: "TNFRGraph",
435
+ cfg_soft: dict[str, Any] | None = None,
436
+ cfg_canon: dict[str, Any] | None = None,
437
+ norms: dict[str, Any] | None = None,
438
+ ):
439
+ self.G = G
440
+ self.cfg_soft = cfg_soft or {}
441
+ self.cfg_canon = cfg_canon or {}
442
+ self.norms = norms or {}
443
+
444
+ @classmethod
445
+ def from_graph(cls, G: "TNFRGraph") -> "GrammarContext":
446
+ """Create context from graph.
447
+
448
+ Parameters
449
+ ----------
450
+ G : TNFRGraph
451
+ Graph to create context from
452
+
453
+ Returns
454
+ -------
455
+ GrammarContext
456
+ New context instance
457
+ """
458
+ return cls(G)
459
+
460
+
461
+ class GrammarValidator:
462
+ """Validates sequences using canonical TNFR grammar constraints.
463
+
464
+ Implements U1-U4 rules that emerge inevitably from TNFR physics.
465
+ This is the single source of truth for grammar validation.
466
+
467
+ All rules derive from:
468
+ - Nodal equation: ∂EPI/∂t = νf · ΔNFR(t)
469
+ - Canonical invariants (AGENTS.md §3)
470
+ - Formal contracts (AGENTS.md §4)
471
+
472
+ No organizational conventions are enforced.
473
+ """
474
+
475
+ @staticmethod
476
+ def validate_initiation(
477
+ sequence: List[Operator],
478
+ epi_initial: float = 0.0,
479
+ ) -> tuple[bool, str]:
480
+ """Validate U1a: Structural initiation.
481
+
482
+ Physical basis: If EPI=0, then ∂EPI/∂t is undefined or zero.
483
+ Cannot evolve structure that doesn't exist.
484
+
485
+ Generators create structure from:
486
+ - AL (Emission): vacuum via emission
487
+ - NAV (Transition): latent EPI via regime shift
488
+ - REMESH (Recursivity): dormant structure across scales
489
+
490
+ Parameters
491
+ ----------
492
+ sequence : List[Operator]
493
+ Sequence of operators to validate
494
+ epi_initial : float, optional
495
+ Initial EPI value (default: 0.0)
496
+
497
+ Returns
498
+ -------
499
+ tuple[bool, str]
500
+ (is_valid, message)
501
+ """
502
+ if epi_initial > 0.0:
503
+ # Already initialized, no generator required
504
+ return True, "U1a: EPI>0, initiation not required"
505
+
506
+ if not sequence:
507
+ return False, "U1a violated: Empty sequence with EPI=0"
508
+
509
+ first_op = getattr(sequence[0], "canonical_name", sequence[0].name.lower())
510
+
511
+ if first_op not in GENERATORS:
512
+ return (
513
+ False,
514
+ f"U1a violated: EPI=0 requires generator (got '{first_op}'). "
515
+ f"Valid: {sorted(GENERATORS)}",
516
+ )
517
+
518
+ return True, f"U1a satisfied: starts with generator '{first_op}'"
519
+
520
+ @staticmethod
521
+ def validate_closure(sequence: List[Operator]) -> tuple[bool, str]:
522
+ """Validate U1b: Structural closure.
523
+
524
+ Physical basis: Sequences are bounded action potentials in structural
525
+ space. Like physical waves, they must have termination that leaves
526
+ system in coherent attractor states.
527
+
528
+ Closures stabilize via:
529
+ - SHA (Silence): Terminal closure - freezes evolution (νf → 0)
530
+ - NAV (Transition): Handoff closure - transfers to next regime
531
+ - REMESH (Recursivity): Recursive closure - distributes across scales
532
+ - OZ (Dissonance): Intentional closure - preserves activation/tension
533
+
534
+ Parameters
535
+ ----------
536
+ sequence : List[Operator]
537
+ Sequence of operators to validate
538
+
539
+ Returns
540
+ -------
541
+ tuple[bool, str]
542
+ (is_valid, message)
543
+ """
544
+ if not sequence:
545
+ return False, "U1b violated: Empty sequence has no closure"
546
+
547
+ last_op = getattr(sequence[-1], "canonical_name", sequence[-1].name.lower())
548
+
549
+ if last_op not in CLOSURES:
550
+ return (
551
+ False,
552
+ f"U1b violated: Sequence must end with closure (got '{last_op}'). "
553
+ f"Valid: {sorted(CLOSURES)}",
554
+ )
555
+
556
+ return True, f"U1b satisfied: ends with closure '{last_op}'"
557
+
558
+ @staticmethod
559
+ def validate_convergence(sequence: List[Operator]) -> tuple[bool, str]:
560
+ """Validate U2: Convergence and boundedness.
561
+
562
+ Physical basis: Without stabilizers, ∫νf·ΔNFR dt → ∞ (diverges).
563
+ Stabilizers provide negative feedback ensuring integral convergence.
564
+
565
+ From integrated nodal equation:
566
+ EPI(t_f) = EPI(t_0) + ∫_{t_0}^{t_f} νf·ΔNFR dτ
567
+
568
+ Without stabilizers:
569
+ d(ΔNFR)/dt > 0 always → ΔNFR ~ e^(λt) → integral diverges
570
+
571
+ With stabilizers (IL or THOL):
572
+ d(ΔNFR)/dt can be < 0 → ΔNFR bounded → integral converges
573
+
574
+ Parameters
575
+ ----------
576
+ sequence : List[Operator]
577
+ Sequence of operators to validate
578
+
579
+ Returns
580
+ -------
581
+ tuple[bool, str]
582
+ (is_valid, message)
583
+ """
584
+ # Check if sequence contains destabilizers
585
+ destabilizers_present = [
586
+ getattr(op, "canonical_name", op.name.lower())
587
+ for op in sequence
588
+ if getattr(op, "canonical_name", op.name.lower()) in DESTABILIZERS
589
+ ]
590
+
591
+ if not destabilizers_present:
592
+ # No destabilizers = no divergence risk
593
+ return True, "U2: not applicable (no destabilizers present)"
594
+
595
+ # Check for stabilizers
596
+ stabilizers_present = [
597
+ getattr(op, "canonical_name", op.name.lower())
598
+ for op in sequence
599
+ if getattr(op, "canonical_name", op.name.lower()) in STABILIZERS
600
+ ]
601
+
602
+ if not stabilizers_present:
603
+ return (
604
+ False,
605
+ f"U2 violated: destabilizers {destabilizers_present} present "
606
+ f"without stabilizer. Integral ∫νf·ΔNFR dt may diverge. "
607
+ f"Add: {sorted(STABILIZERS)}",
608
+ )
609
+
610
+ return (
611
+ True,
612
+ f"U2 satisfied: stabilizers {stabilizers_present} "
613
+ f"bound destabilizers {destabilizers_present}",
614
+ )
615
+
616
+ @staticmethod
617
+ def validate_resonant_coupling(sequence: List[Operator]) -> tuple[bool, str]:
618
+ """Validate U3: Resonant coupling.
619
+
620
+ Physical basis: AGENTS.md Invariant #5 states "no coupling is valid
621
+ without explicit phase verification (synchrony)".
622
+
623
+ Resonance physics requires phase compatibility:
624
+ |φᵢ - φⱼ| ≤ Δφ_max
625
+
626
+ Without phase verification:
627
+ Nodes with incompatible phases (antiphase) could attempt coupling
628
+ → Destructive interference → Violates resonance physics
629
+
630
+ With phase verification:
631
+ Only synchronous nodes couple → Constructive interference
632
+
633
+ Parameters
634
+ ----------
635
+ sequence : List[Operator]
636
+ Sequence of operators to validate
637
+
638
+ Returns
639
+ -------
640
+ tuple[bool, str]
641
+ (is_valid, message)
642
+
643
+ Notes
644
+ -----
645
+ U3 is a META-rule: it requires that when UM (Coupling) or RA (Resonance)
646
+ operators are used, the implementation MUST verify phase compatibility.
647
+ The actual phase check happens in operator preconditions.
648
+
649
+ This grammar rule documents the requirement and ensures awareness
650
+ that phase checks are MANDATORY (Invariant #5), not optional.
651
+ """
652
+ # Check if sequence contains coupling/resonance operators
653
+ coupling_ops = [
654
+ getattr(op, "canonical_name", op.name.lower())
655
+ for op in sequence
656
+ if getattr(op, "canonical_name", op.name.lower()) in COUPLING_RESONANCE
657
+ ]
658
+
659
+ if not coupling_ops:
660
+ # No coupling/resonance = U3 not applicable
661
+ return True, "U3: not applicable (no coupling/resonance operators)"
662
+
663
+ # U3 satisfied: Sequence contains coupling/resonance
664
+ # Phase verification is MANDATORY per Invariant #5
665
+ # Actual check happens in operator preconditions
666
+ return (
667
+ True,
668
+ f"U3 awareness: operators {coupling_ops} require phase verification "
669
+ f"(MANDATORY per Invariant #5). Enforced in preconditions.",
670
+ )
671
+
672
+ @staticmethod
673
+ def validate_bifurcation_triggers(sequence: List[Operator]) -> tuple[bool, str]:
674
+ """Validate U4a: Bifurcation triggers need handlers.
675
+
676
+ Physical basis: AGENTS.md Contract OZ states dissonance may trigger
677
+ bifurcation if ∂²EPI/∂t² > τ. When bifurcation is triggered, handlers
678
+ are required to manage structural reorganization.
679
+
680
+ Bifurcation physics:
681
+ If ∂²EPI/∂t² > τ → multiple reorganization paths viable
682
+ → System enters bifurcation regime
683
+ → Requires handlers (THOL or IL) for stable transition
684
+
685
+ Parameters
686
+ ----------
687
+ sequence : List[Operator]
688
+ Sequence of operators to validate
689
+
690
+ Returns
691
+ -------
692
+ tuple[bool, str]
693
+ (is_valid, message)
694
+ """
695
+ # Check if sequence contains bifurcation triggers
696
+ trigger_ops = [
697
+ getattr(op, "canonical_name", op.name.lower())
698
+ for op in sequence
699
+ if getattr(op, "canonical_name", op.name.lower()) in BIFURCATION_TRIGGERS
700
+ ]
701
+
702
+ if not trigger_ops:
703
+ # No triggers = U4a not applicable
704
+ return True, "U4a: not applicable (no bifurcation triggers)"
705
+
706
+ # Check for handlers
707
+ handler_ops = [
708
+ getattr(op, "canonical_name", op.name.lower())
709
+ for op in sequence
710
+ if getattr(op, "canonical_name", op.name.lower()) in BIFURCATION_HANDLERS
711
+ ]
712
+
713
+ if not handler_ops:
714
+ return (
715
+ False,
716
+ f"U4a violated: bifurcation triggers {trigger_ops} present "
717
+ f"without handler. If ∂²EPI/∂t² > τ, bifurcation may occur unmanaged. "
718
+ f"Add: {sorted(BIFURCATION_HANDLERS)}",
719
+ )
720
+
721
+ return (
722
+ True,
723
+ f"U4a satisfied: bifurcation triggers {trigger_ops} "
724
+ f"have handlers {handler_ops}",
725
+ )
726
+
727
+ @staticmethod
728
+ def validate_transformer_context(sequence: List[Operator]) -> tuple[bool, str]:
729
+ """Validate U4b: Transformers need context.
730
+
731
+ Physical basis: Bifurcations require threshold energy to cross
732
+ critical points. Transformers (ZHIR, THOL) need recent destabilizers
733
+ to provide sufficient |ΔNFR| for phase transitions.
734
+
735
+ ZHIR (Mutation) requirements:
736
+ 1. Prior IL: Stable base prevents transformation from chaos
737
+ 2. Recent destabilizer: Threshold energy for bifurcation
738
+
739
+ THOL (Self-organization) requirements:
740
+ 1. Recent destabilizer: Disorder to self-organize
741
+
742
+ "Recent" = within ~3 operators (ΔNFR decays via structural relaxation)
743
+
744
+ Parameters
745
+ ----------
746
+ sequence : List[Operator]
747
+ Sequence of operators to validate
748
+
749
+ Returns
750
+ -------
751
+ tuple[bool, str]
752
+ (is_valid, message)
753
+
754
+ Notes
755
+ -----
756
+ This implements "graduated destabilization" - transformers need
757
+ sufficient ΔNFR context. The ~3 operator window captures when
758
+ |ΔNFR| remains above bifurcation threshold.
759
+ """
760
+ # Check if sequence contains transformers
761
+ transformer_ops = []
762
+ for i, op in enumerate(sequence):
763
+ op_name = getattr(op, "canonical_name", op.name.lower())
764
+ if op_name in TRANSFORMERS:
765
+ transformer_ops.append((i, op_name))
766
+
767
+ if not transformer_ops:
768
+ return True, "U4b: not applicable (no transformers)"
769
+
770
+ # For each transformer, check context
771
+ violations = []
772
+ for idx, transformer_name in transformer_ops:
773
+ # Check for recent destabilizer (within 3 operators before)
774
+ window_start = max(0, idx - 3)
775
+ recent_destabilizers = []
776
+ prior_il = False
777
+
778
+ for j in range(window_start, idx):
779
+ op_name = getattr(
780
+ sequence[j], "canonical_name", sequence[j].name.lower()
781
+ )
782
+ if op_name in DESTABILIZERS:
783
+ recent_destabilizers.append((j, op_name))
784
+ if op_name == "coherence":
785
+ prior_il = True
786
+
787
+ # Check requirements
788
+ if not recent_destabilizers:
789
+ violations.append(
790
+ f"{transformer_name} at position {idx} lacks recent destabilizer "
791
+ f"(none in window [{window_start}:{idx}]). "
792
+ f"Need: {sorted(DESTABILIZERS)}"
793
+ )
794
+
795
+ # Additional requirement for ZHIR: prior IL
796
+ if transformer_name == "mutation" and not prior_il:
797
+ violations.append(
798
+ f"mutation at position {idx} lacks prior IL (coherence) "
799
+ f"for stable transformation base"
800
+ )
801
+
802
+ if violations:
803
+ return (False, f"U4b violated: {'; '.join(violations)}")
804
+
805
+ return (True, f"U4b satisfied: transformers have proper context")
806
+
807
+ @staticmethod
808
+ def validate_remesh_amplification(sequence: List[Operator]) -> tuple[bool, str]:
809
+ """Validate U2-REMESH: Recursive amplification control.
810
+
811
+ Physical basis: REMESH implements temporal coupling EPI(t) ↔ EPI(t-τ)
812
+ which creates feedback that amplifies structural changes. When combined
813
+ with destabilizers, this can cause unbounded growth.
814
+
815
+ From integrated nodal equation:
816
+ EPI(t_f) = EPI(t_0) + ∫_{t_0}^{t_f} νf·ΔNFR dτ
817
+
818
+ REMESH temporal mixing:
819
+ EPI_mixed = (1-α)·EPI_now + α·EPI_past
820
+
821
+ Without stabilizers:
822
+ REMESH + destabilizers → recursive amplification
823
+ → ∫ νf·ΔNFR dt → ∞ (feedback loop)
824
+ → System fragments
825
+
826
+ With stabilizers:
827
+ IL or THOL provides negative feedback
828
+ → Bounded recursive evolution
829
+ → ∫ νf·ΔNFR dt < ∞
830
+
831
+ Specific combinations requiring stabilizers:
832
+ - REMESH + VAL: Recursive expansion needs coherence stabilization
833
+ - REMESH + OZ: Recursive bifurcation needs self-organization handlers
834
+ - REMESH + ZHIR: Replicative mutation needs coherence consolidation
835
+
836
+ Parameters
837
+ ----------
838
+ sequence : List[Operator]
839
+ Sequence of operators to validate
840
+
841
+ Returns
842
+ -------
843
+ tuple[bool, str]
844
+ (is_valid, message)
845
+
846
+ Notes
847
+ -----
848
+ This rule is DISTINCT from general U2 (convergence). While U2 checks
849
+ for destabilizers needing stabilizers, U2-REMESH specifically addresses
850
+ REMESH's amplification property: it multiplies the effect of destabilizers
851
+ through recursive feedback across temporal/spatial scales.
852
+
853
+ Physical derivation: See src/tnfr/operators/remesh.py module docstring,
854
+ section "Grammar Implications from Physical Analysis" → U2: CONVERGENCE.
855
+ """
856
+ # Check if sequence contains REMESH
857
+ has_remesh = any(
858
+ getattr(op, "canonical_name", op.name.lower()) == "recursivity"
859
+ for op in sequence
860
+ )
861
+
862
+ if not has_remesh:
863
+ return True, "U2-REMESH: not applicable (no recursivity present)"
864
+
865
+ # Check for destabilizers
866
+ destabilizers_present = [
867
+ getattr(op, "canonical_name", op.name.lower())
868
+ for op in sequence
869
+ if getattr(op, "canonical_name", op.name.lower()) in DESTABILIZERS
870
+ ]
871
+
872
+ if not destabilizers_present:
873
+ return True, "U2-REMESH: satisfied (no destabilizers to amplify)"
874
+
875
+ # Check for stabilizers
876
+ stabilizers_present = [
877
+ getattr(op, "canonical_name", op.name.lower())
878
+ for op in sequence
879
+ if getattr(op, "canonical_name", op.name.lower()) in STABILIZERS
880
+ ]
881
+
882
+ if not stabilizers_present:
883
+ return (
884
+ False,
885
+ f"U2-REMESH violated: recursivity amplifies destabilizers "
886
+ f"{destabilizers_present} via recursive feedback. "
887
+ f"Integral ∫νf·ΔNFR dt may diverge (unbounded growth). "
888
+ f"Required: {sorted(STABILIZERS)} to bound recursive amplification",
889
+ )
890
+
891
+ return (
892
+ True,
893
+ f"U2-REMESH satisfied: stabilizers {stabilizers_present} "
894
+ f"bound recursive amplification of {destabilizers_present}",
895
+ )
896
+
897
+ @classmethod
898
+ def validate(
899
+ cls,
900
+ sequence: List[Operator],
901
+ epi_initial: float = 0.0,
902
+ ) -> tuple[bool, List[str]]:
903
+ """Validate sequence using all unified canonical constraints.
904
+
905
+ This validates pure TNFR physics:
906
+ - U1: Structural initiation & closure
907
+ - U2: Convergence & boundedness
908
+ - U3: Resonant coupling
909
+ - U4: Bifurcation dynamics
910
+
911
+ Parameters
912
+ ----------
913
+ sequence : List[Operator]
914
+ Sequence to validate
915
+ epi_initial : float, optional
916
+ Initial EPI value (default: 0.0)
917
+
918
+ Returns
919
+ -------
920
+ tuple[bool, List[str]]
921
+ (is_valid, messages)
922
+ is_valid: True if all constraints satisfied
923
+ messages: List of validation messages
924
+ """
925
+ messages = []
926
+ all_valid = True
927
+
928
+ # U1a: Initiation
929
+ valid_init, msg_init = cls.validate_initiation(sequence, epi_initial)
930
+ messages.append(f"U1a: {msg_init}")
931
+ all_valid = all_valid and valid_init
932
+
933
+ # U1b: Closure
934
+ valid_closure, msg_closure = cls.validate_closure(sequence)
935
+ messages.append(f"U1b: {msg_closure}")
936
+ all_valid = all_valid and valid_closure
937
+
938
+ # U2: Convergence
939
+ valid_conv, msg_conv = cls.validate_convergence(sequence)
940
+ messages.append(f"U2: {msg_conv}")
941
+ all_valid = all_valid and valid_conv
942
+
943
+ # U3: Resonant coupling
944
+ valid_coupling, msg_coupling = cls.validate_resonant_coupling(sequence)
945
+ messages.append(f"U3: {msg_coupling}")
946
+ all_valid = all_valid and valid_coupling
947
+
948
+ # U4a: Bifurcation triggers
949
+ valid_triggers, msg_triggers = cls.validate_bifurcation_triggers(sequence)
950
+ messages.append(f"U4a: {msg_triggers}")
951
+ all_valid = all_valid and valid_triggers
952
+
953
+ # U4b: Transformer context
954
+ valid_context, msg_context = cls.validate_transformer_context(sequence)
955
+ messages.append(f"U4b: {msg_context}")
956
+ all_valid = all_valid and valid_context
957
+
958
+ # U2-REMESH: Recursive amplification control
959
+ valid_remesh, msg_remesh = cls.validate_remesh_amplification(sequence)
960
+ messages.append(f"U2-REMESH: {msg_remesh}")
961
+ all_valid = all_valid and valid_remesh
962
+
963
+ return all_valid, messages
964
+
965
+
966
+ def validate_grammar(
967
+ sequence: List[Operator],
968
+ epi_initial: float = 0.0,
969
+ ) -> bool:
970
+ """Validate sequence using canonical TNFR grammar constraints.
971
+
972
+ Convenience function that returns only boolean result.
973
+ For detailed messages, use GrammarValidator.validate().
974
+
975
+ Parameters
976
+ ----------
977
+ sequence : List[Operator]
978
+ Sequence of operators to validate
979
+ epi_initial : float, optional
980
+ Initial EPI value (default: 0.0)
981
+
982
+ Returns
983
+ -------
984
+ bool
985
+ True if sequence satisfies all canonical constraints
986
+
987
+ Examples
988
+ --------
989
+ >>> from tnfr.operators.definitions import Emission, Coherence, Silence
990
+ >>> ops = [Emission(), Coherence(), Silence()]
991
+ >>> validate_grammar(ops, epi_initial=0.0) # doctest: +SKIP
992
+ True
993
+
994
+ Notes
995
+ -----
996
+ This validator is 100% physics-based. All constraints emerge from:
997
+ - Nodal equation: ∂EPI/∂t = νf · ΔNFR(t)
998
+ - TNFR invariants (AGENTS.md §3)
999
+ - Formal operator contracts (AGENTS.md §4)
1000
+
1001
+ See UNIFIED_GRAMMAR_RULES.md for complete derivations.
1002
+ """
1003
+ is_valid, _ = GrammarValidator.validate(sequence, epi_initial)
1004
+ return is_valid
1005
+
1006
+
1007
+ # ============================================================================
1008
+ # Grammar Application Functions (Minimal Stubs for Import Compatibility)
1009
+ # ============================================================================
1010
+
1011
+
1012
+ def apply_glyph_with_grammar(
1013
+ G: "TNFRGraph",
1014
+ nodes: Any,
1015
+ glyph: Any,
1016
+ window: Any = None,
1017
+ ) -> None:
1018
+ """Apply glyph to nodes with grammar validation.
1019
+
1020
+ Applies the specified glyph to each node in the iterable using the canonical
1021
+ TNFR operator implementation.
1022
+
1023
+ Parameters
1024
+ ----------
1025
+ G : TNFRGraph
1026
+ Graph containing nodes
1027
+ nodes : Any
1028
+ Node, list of nodes, or node iterable to apply glyph to
1029
+ glyph : Any
1030
+ Glyph to apply
1031
+ window : Any, optional
1032
+ Grammar window constraint
1033
+
1034
+ Notes
1035
+ -----
1036
+ This function delegates to apply_glyph for each node, which wraps
1037
+ the node in NodeNX and applies the glyph operation.
1038
+ """
1039
+ from . import apply_glyph
1040
+
1041
+ # Handle single node or iterable of nodes
1042
+ # Check if it's a single hashable node or an iterable
1043
+ try:
1044
+ # Try to treat as single hashable node
1045
+ hash(nodes)
1046
+ # If hashable, it's a single node
1047
+ nodes_iter = [nodes]
1048
+ except (TypeError, AttributeError):
1049
+ # Not hashable, treat as iterable
1050
+ # Convert to list to allow multiple iterations if needed
1051
+ try:
1052
+ nodes_iter = list(nodes)
1053
+ except TypeError:
1054
+ # If not iterable, wrap in list
1055
+ nodes_iter = [nodes]
1056
+
1057
+ for node in nodes_iter:
1058
+ apply_glyph(G, node, glyph, window=window)
1059
+
1060
+
1061
+ def on_applied_glyph(G: "TNFRGraph", n: "NodeId", applied: Any) -> None:
1062
+ """Record glyph application in node history.
1063
+
1064
+ Minimal stub for tracking operator sequences.
1065
+
1066
+ Parameters
1067
+ ----------
1068
+ G : TNFRGraph
1069
+ Graph containing node
1070
+ n : NodeId
1071
+ Node identifier
1072
+ applied : Any
1073
+ Applied glyph or operator name
1074
+ """
1075
+ # Minimal stub for telemetry
1076
+ if "glyph_history" not in G.nodes[n]:
1077
+ G.nodes[n]["glyph_history"] = []
1078
+ G.nodes[n]["glyph_history"].append(applied)
1079
+
1080
+
1081
+ def enforce_canonical_grammar(
1082
+ G: "TNFRGraph",
1083
+ n: "NodeId",
1084
+ cand: Any,
1085
+ ctx: Any = None,
1086
+ ) -> Any:
1087
+ """Enforce canonical grammar constraints before operator application.
1088
+
1089
+ Minimal stub implementation.
1090
+
1091
+ Parameters
1092
+ ----------
1093
+ G : TNFRGraph
1094
+ Graph containing node
1095
+ n : NodeId
1096
+ Node identifier
1097
+ cand : Any
1098
+ Candidate glyph/operator
1099
+ ctx : Any, optional
1100
+ Grammar context
1101
+
1102
+ Returns
1103
+ -------
1104
+ Any
1105
+ Validated glyph/operator
1106
+ """
1107
+ # Minimal stub - return candidate as-is
1108
+ return cand
1109
+
1110
+
1111
+ def validate_sequence(
1112
+ names: Any = None,
1113
+ **kwargs: Any,
1114
+ ) -> Any:
1115
+ """Validate sequence of operator names.
1116
+
1117
+ Minimal stub implementation for import compatibility.
1118
+
1119
+ Parameters
1120
+ ----------
1121
+ names : Iterable[str] | object, optional
1122
+ Sequence of operator names
1123
+ **kwargs : Any
1124
+ Additional validation options
1125
+
1126
+ Returns
1127
+ -------
1128
+ ValidationOutcome
1129
+ Validation result (stub returns success)
1130
+ """
1131
+ # Minimal stub - return success
1132
+ class ValidationStub:
1133
+ def __init__(self):
1134
+ self.passed = True
1135
+ self.message = "Validation stub"
1136
+ self.metadata = {}
1137
+ return ValidationStub()
1138
+
1139
+
1140
+ def parse_sequence(names: Any) -> Any:
1141
+ """Parse sequence of operator names.
1142
+
1143
+ Minimal stub implementation.
1144
+
1145
+ Parameters
1146
+ ----------
1147
+ names : Iterable[str]
1148
+ Sequence of operator names
1149
+
1150
+ Returns
1151
+ -------
1152
+ SequenceValidationResult
1153
+ Parse result (stub)
1154
+ """
1155
+ # Minimal stub
1156
+ class ParseStub:
1157
+ def __init__(self):
1158
+ self.tokens = list(names) if names else []
1159
+ self.canonical_tokens = self.tokens
1160
+ self.passed = True
1161
+ self.message = "Parse stub"
1162
+ self.metadata = {}
1163
+ self.error = None
1164
+ return ParseStub()