axis-synome 0.1.0.dev16__tar.gz → 0.1.0.dev27__tar.gz

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.
Files changed (74) hide show
  1. {axis_synome-0.1.0.dev16/src/axis_synome.egg-info → axis_synome-0.1.0.dev27}/PKG-INFO +26 -1
  2. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/README.md +23 -0
  3. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/WRITING_SPECS.md +30 -34
  4. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/pyproject.toml +8 -2
  5. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/src/axis_synome/_version.py +2 -2
  6. axis_synome-0.1.0.dev27/src/axis_synome/spec_support/runtime/base.py +93 -0
  7. axis_synome-0.1.0.dev27/src/axis_synome/spec_support/runtime/math.py +32 -0
  8. axis_synome-0.1.0.dev27/src/axis_synome/spec_support/runtime/reference.py +20 -0
  9. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/src/axis_synome/spec_validator/checker.py +37 -7
  10. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/src/axis_synome/spec_validator/python_subset.py +2 -0
  11. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27/src/axis_synome.egg-info}/PKG-INFO +26 -1
  12. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/src/axis_synome.egg-info/SOURCES.txt +8 -2
  13. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/src/axis_synome.egg-info/requires.txt +3 -0
  14. axis_synome-0.1.0.dev27/tests/axis_synome/risk_capital/formulas/__init__.py +0 -0
  15. axis_synome-0.1.0.dev27/tests/axis_synome/spec_support/__init__.py +0 -0
  16. axis_synome-0.1.0.dev27/tests/axis_synome/spec_support/runtime/__init__.py +0 -0
  17. axis_synome-0.1.0.dev27/tests/axis_synome/spec_support/runtime/test_base.py +52 -0
  18. axis_synome-0.1.0.dev27/tests/axis_synome/spec_support/runtime/test_math.py +30 -0
  19. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/tests/axis_synome/spec_validator/test_checker.py +89 -5
  20. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/tests/axis_synome/spec_validator/test_flake8_plugin.py +1 -1
  21. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/uv.lock +108 -20
  22. axis_synome-0.1.0.dev16/src/axis_synome/spec_support/metadata.py +0 -22
  23. axis_synome-0.1.0.dev16/tests/axis_synome/asc/test_asc_spec_client_parity.py +0 -235
  24. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/.flake8 +0 -0
  25. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/setup.cfg +0 -0
  26. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/src/axis_synome/__init__.py +0 -0
  27. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/src/axis_synome/spec/__init__.py +0 -0
  28. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/src/axis_synome/spec/asc/README.md +0 -0
  29. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/src/axis_synome/spec/asc/entities/__init__.py +0 -0
  30. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/src/axis_synome/spec/asc/entities/assets.py +0 -0
  31. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/src/axis_synome/spec/asc/entities/assets_by_prime.py +0 -0
  32. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/src/axis_synome/spec/asc/entities/networks.py +0 -0
  33. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/src/axis_synome/spec/asc/entities/primes.py +0 -0
  34. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/src/axis_synome/spec/asc/entities/protocol_sets.py +0 -0
  35. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/src/axis_synome/spec/asc/entities/tokens.py +0 -0
  36. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/src/axis_synome/spec/asc/entities/types.py +0 -0
  37. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/src/axis_synome/spec/asc/formulas/asc.py +0 -0
  38. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/src/axis_synome/spec/asc/formulas/asc_collateral_ratio.py +0 -0
  39. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/src/axis_synome/spec/asc/formulas/asc_incentive.py +0 -0
  40. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/src/axis_synome/spec/asc/formulas/dab.py +0 -0
  41. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/src/axis_synome/spec/asc/formulas/latent_asc.py +0 -0
  42. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/src/axis_synome/spec/asc/formulas/ratio_latent_asc.py +0 -0
  43. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/src/axis_synome/spec/asc/formulas/resting_asc.py +0 -0
  44. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/src/axis_synome/spec/codegen_test/entities/agents.py +0 -0
  45. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/src/axis_synome/spec/crypto_lending/__init__.py +0 -0
  46. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/src/axis_synome/spec/crypto_lending/formulas/__init__.py +0 -0
  47. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/src/axis_synome/spec/crypto_lending/formulas/lif.py +0 -0
  48. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/src/axis_synome/spec/risk_capital/__init__.py +0 -0
  49. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/src/axis_synome/spec/risk_capital/formulas/__init__.py +0 -0
  50. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/src/axis_synome/spec/risk_capital/formulas/required_risk_capital.py +0 -0
  51. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/src/axis_synome/spec_support/__init__.py +0 -0
  52. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/src/axis_synome/spec_support/evm_address.py +0 -0
  53. {axis_synome-0.1.0.dev16/tests/axis_synome/risk_capital → axis_synome-0.1.0.dev27/src/axis_synome/spec_support/runtime}/__init__.py +0 -0
  54. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/src/axis_synome/spec_support/validated_dataclass.py +0 -0
  55. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/src/axis_synome/spec_support/validated_str.py +0 -0
  56. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/src/axis_synome/spec_validator/__init__.py +0 -0
  57. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/src/axis_synome/spec_validator/flake8_plugin.py +0 -0
  58. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/src/axis_synome.egg-info/dependency_links.txt +0 -0
  59. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/src/axis_synome.egg-info/entry_points.txt +0 -0
  60. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/src/axis_synome.egg-info/top_level.txt +0 -0
  61. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/tests/axis_synome/asc/__init__.py +0 -0
  62. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/tests/axis_synome/asc/conftest.py +0 -0
  63. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/tests/axis_synome/asc/mocks.py +0 -0
  64. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/tests/axis_synome/asc/test_asc.py +0 -0
  65. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/tests/axis_synome/asc/test_asc_collateral_ratio.py +0 -0
  66. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/tests/axis_synome/asc/test_asc_incentive.py +0 -0
  67. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/tests/axis_synome/asc/test_dab.py +0 -0
  68. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/tests/axis_synome/asc/test_evm_address.py +0 -0
  69. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/tests/axis_synome/asc/test_latent_asc.py +0 -0
  70. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/tests/axis_synome/asc/test_prime_agent_data_validation.py +0 -0
  71. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/tests/axis_synome/asc/test_ratio_latent_asc.py +0 -0
  72. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/tests/axis_synome/asc/test_resting_asc.py +0 -0
  73. {axis_synome-0.1.0.dev16/tests/axis_synome/risk_capital/formulas → axis_synome-0.1.0.dev27/tests/axis_synome/risk_capital}/__init__.py +0 -0
  74. {axis_synome-0.1.0.dev16 → axis_synome-0.1.0.dev27}/tests/axis_synome/risk_capital/formulas/test_loss_given_default.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: axis-synome
3
- Version: 0.1.0.dev16
3
+ Version: 0.1.0.dev27
4
4
  Summary: Axis specification modules (entities, formulas, validators)
5
5
  Author-email: Archon Tech <hello@archontech.ai>
6
6
  License: MIT
@@ -10,6 +10,8 @@ Requires-Python: >=3.11
10
10
  Description-Content-Type: text/markdown
11
11
  Requires-Dist: eth-hash[pycryptodome]>=0.5.0
12
12
  Requires-Dist: pydantic>=2.0.0
13
+ Provides-Extra: runtime
14
+ Requires-Dist: numpy>=1.26; extra == "runtime"
13
15
 
14
16
  # axis-synome
15
17
 
@@ -18,6 +20,7 @@ A Python framework for defining and validating financial specifications, with a
18
20
  - **ASC Specifications** — Actively Stabilizing Capital entities (assets, tokens, networks, protocols) and formulas (collateral ratios, incentives, resting/latent ASC)
19
21
  - **Spec Validator** — Flake8 plugin for enforcing Python subset constraints on spec files
20
22
  - **Metadata & Utilities** — Support for spec documentation and validated dataclasses
23
+ - **SpecRuntime** — Pluggable runtime abstraction for opaque operations (linear interpolation, ...) that specs can call without depending on a specific implementation library
21
24
 
22
25
  ## Installation
23
26
 
@@ -27,6 +30,12 @@ Install from PyPI:
27
30
  pip install axis-synome
28
31
  ```
29
32
 
33
+ To enable the built-in `ReferenceSpecRuntime` (numpy-backed), install with the `runtime` extra:
34
+
35
+ ```bash
36
+ pip install axis-synome[runtime]
37
+ ```
38
+
30
39
  Or install from a private registry:
31
40
 
32
41
  ```bash
@@ -47,6 +56,22 @@ from axis_synome.spec.asc.entities import primes, tokens, networks, protocol_set
47
56
  from axis_synome.spec.asc.formulas import asc, asc_collateral_ratio, asc_incentive
48
57
  ```
49
58
 
59
+ ### Opaque Operations in Specs
60
+
61
+ ```python
62
+ from axis_synome.spec_support.runtime import math
63
+
64
+ values = math.linear_interp([1.5], [1.0, 2.0], [10.0, 20.0]) # -> [15.0]
65
+ ```
66
+
67
+ The active runtime is selected once at import time. Resolution order:
68
+
69
+ 1. `AXIS_SYNOME_SPEC_RUNTIME=module.path:ClassName` env var
70
+ 2. A registered `axis_synome.spec_runtimes` entry point (declared by a consumer package's `pyproject.toml`)
71
+ 3. The bundled `ReferenceSpecRuntime` (requires `axis-synome[runtime]`)
72
+
73
+ If none is available, importing `axis_synome.spec_support.runtime.math` raises `ImportError` — the runtime is required at import time, not on first call.
74
+
50
75
  ### Spec Validation
51
76
 
52
77
  ```python
@@ -5,6 +5,7 @@ A Python framework for defining and validating financial specifications, with a
5
5
  - **ASC Specifications** — Actively Stabilizing Capital entities (assets, tokens, networks, protocols) and formulas (collateral ratios, incentives, resting/latent ASC)
6
6
  - **Spec Validator** — Flake8 plugin for enforcing Python subset constraints on spec files
7
7
  - **Metadata & Utilities** — Support for spec documentation and validated dataclasses
8
+ - **SpecRuntime** — Pluggable runtime abstraction for opaque operations (linear interpolation, ...) that specs can call without depending on a specific implementation library
8
9
 
9
10
  ## Installation
10
11
 
@@ -14,6 +15,12 @@ Install from PyPI:
14
15
  pip install axis-synome
15
16
  ```
16
17
 
18
+ To enable the built-in `ReferenceSpecRuntime` (numpy-backed), install with the `runtime` extra:
19
+
20
+ ```bash
21
+ pip install axis-synome[runtime]
22
+ ```
23
+
17
24
  Or install from a private registry:
18
25
 
19
26
  ```bash
@@ -34,6 +41,22 @@ from axis_synome.spec.asc.entities import primes, tokens, networks, protocol_set
34
41
  from axis_synome.spec.asc.formulas import asc, asc_collateral_ratio, asc_incentive
35
42
  ```
36
43
 
44
+ ### Opaque Operations in Specs
45
+
46
+ ```python
47
+ from axis_synome.spec_support.runtime import math
48
+
49
+ values = math.linear_interp([1.5], [1.0, 2.0], [10.0, 20.0]) # -> [15.0]
50
+ ```
51
+
52
+ The active runtime is selected once at import time. Resolution order:
53
+
54
+ 1. `AXIS_SYNOME_SPEC_RUNTIME=module.path:ClassName` env var
55
+ 2. A registered `axis_synome.spec_runtimes` entry point (declared by a consumer package's `pyproject.toml`)
56
+ 3. The bundled `ReferenceSpecRuntime` (requires `axis-synome[runtime]`)
57
+
58
+ If none is available, importing `axis_synome.spec_support.runtime.math` raises `ImportError` — the runtime is required at import time, not on first call.
59
+
37
60
  ### Spec Validation
38
61
 
39
62
  ```python
@@ -175,6 +175,34 @@ a fixed set instiantiated objects. `value_usd` is additionally restricted with a
175
175
  - Uplift to shared only when needed: introduce shared entities at top level (`axis/entities/`) when the same entities are used by multiple formulas and sharing materially reduces duplication. A pragmatic threshold is when a third, independent formula reuses the same entities.
176
176
  - Don’t add empty common folders: only create and populate shared folders when you actually have shared entities.
177
177
  - Moving entities is a breaking change: import paths change when uplifting to shared. Provide re‑exports where feasible during transitions and bump the major version when breaking import paths.
178
+
179
+ ### SpecRuntime (Opaque Functions)
180
+
181
+ Use only when the operation cannot be expressed in the allowed, parser‑friendly subset, or is so complex (e.g., ARIMA/GARCH/ML/DNN) that a full mathematical spec is infeasible. Prefer parser‑friendly constructs first (comprehensions + sum/min/max/any/all, simple math.*, scalar per‑element helpers).
182
+
183
+ - Use `axis_synome.spec_support.runtime` (Built‑in SpecRuntime)
184
+ - What: a small set of opaque primitives exposed as free functions under `axis_synome.spec_support.runtime.<domain>` (e.g. `runtime.math.linear_interp`). Calls are dispatched to a `SpecRuntime` resolved once at package import time. The parser treats these calls as uninterpreted; the runtime implementation can use NumPy/StatsModels/etc.
185
+ - When: the operation truly requires external libraries (e.g., interpolation, rolling_var, GARCH/ARIMA); you need production‑grade runtime while keeping the spec parseable.
186
+ - How (spec author):
187
+ - Import the relevant submodule and call the function directly:
188
+ - `from axis_synome.spec_support.runtime import math`
189
+ - `math.linear_interp(x, xp, fp)`
190
+ - Do not call `get_runtime()` directly from spec code; treat the `SpecRuntime` class as an internal extension point.
191
+ - How (runtime selection at deploy/test time): in priority order,
192
+ 1. `AXIS_SYNOME_SPEC_RUNTIME=my_pkg.module:MyRuntime` env var.
193
+ 2. Register an entry point in the consuming package's `pyproject.toml`:
194
+ ```toml
195
+ [project.entry-points."axis_synome.spec_runtimes"]
196
+ default = "my_pkg.module:MyRuntime"
197
+ ```
198
+ 3. Otherwise, the bundled `ReferenceSpecRuntime` (numpy‑backed, requires the `[runtime]` extra) is used.
199
+ - Adding a new operation: extend `SpecRuntime` with a new abstract method, implement it in `ReferenceSpecRuntime`, and add a thin wrapper in `axis_synome/spec_support/runtime/<domain>.py` that simply calls `get_runtime().<op>(...)`.
200
+
201
+ - Authoring Rules
202
+ - Keep wrappers zero‑logic (no exceptions/branching); just delegate to `get_runtime()`.
203
+ - Use precise, stable types and consistent snake_case names.
204
+ - Briefly document new opaque functions in `runtime/README.md` (purpose, signature, runtime notes).
205
+
178
206
  ### Formulas
179
207
 
180
208
  - Formula functions are pure: `(inputs) -> output`, but process inputs to intermediate values by calling other formula functions (or by utilizing a restricted set of built-in functions and external functions)
@@ -299,13 +327,6 @@ Assuming that `PrimeAgentAll` is an Enum consisting of prime agent instances, to
299
327
  - Location: keep tests under `python/axis_synome/tests/` organized by concept/module. Treat these as internal by default; they can be published later based on demand.
300
328
  - Declarative first: specs are declarative and amenable to static verification; tests complement by documenting usage and catching regressions in opaque integrations.
301
329
 
302
- #### Reference Implementations for Opaque Functions
303
-
304
- - Opaque functions (e.g., ARIMA) are too complex to fully specify as pure math. To execute happy‑path tests, provide a minimal “test math engine” or reference implementation used only by tests.
305
- - Do not publish as an official runtime dependency initially. If users request a ready‑made engine, consider publishing a separate package later.
306
- - Multiple valid approaches may exist; tests should pick one concrete implementation as an example without constraining others.
307
-
308
-
309
330
  ## Starter Template
310
331
 
311
332
  Start a new spec module with (simplified example; in real modules, entities typically live in their own package folder):
@@ -391,7 +412,7 @@ def threshold_satisfied(owner: EligibleOwner) -> bool:
391
412
  - Aggregators: `sum`, `min`, `max`, `any`, `all` over generator expressions.
392
413
  - Generator expressions and simple `range(...)` where needed.
393
414
  - Selected `math.*` (e.g., `log`, `exp`, `sqrt`, `pow`).
394
- - Imports from `axis_synome.spec`, `synome`, `typing`, `dataclasses`, `enum`, `pydantic`, and bare `math`.
415
+ - Imports from `axis_synome.spec`, `axis_synome.spec_support`, `synome`, `typing`, `dataclasses`, `enum`, `pydantic`, and bare `math`.
395
416
 
396
417
  - Avoid / Disallowed
397
418
  - Mutation, I/O, networking, side effects; exceptions and `try/except`.
@@ -493,7 +514,7 @@ Use the Makefile targets to run checks consistently:
493
514
 
494
515
  - For-loops inside formulas → Replace with generator expressions plus `sum(...)`, `any(...)`, or `all(...)`.
495
516
  - Dict/set comprehensions → Move structure-building to entities or use lists/tuples; keep formula bodies expression-centric.
496
- - Unapproved builtins/imports → Use only builtins in the allowlist and imports under permitted prefixes (`axis_synome.spec`, `typing`, `dataclasses`, `enum`, `pydantic`, `synome`, or `math`).
517
+ - Unapproved builtins/imports → Use only builtins in the allowlist and imports under permitted prefixes (`axis_synome.spec`, `axis_synome.spec_support`, `typing`, `dataclasses`, `enum`, `pydantic`, `synome`, or `math`).
497
518
  - Methods in dataclasses/Enums → Move logic into free functions; limit classes to annotated fields (dataclasses) or constant members (Enums).
498
519
  - Missing/loose types → Add precise argument and return annotations; prefer closed-world Enums where policy implies eligibility.
499
520
 
@@ -646,31 +667,6 @@ def all_positions_above(positions: list[float], threshold: float) -> bool:
646
667
  return all(p >= threshold for p in positions)
647
668
  ```
648
669
 
649
- ## Opaque Functions (Use Sparingly)
650
-
651
- Use only when the operation cannot be expressed in the allowed, parser‑friendly subset, or is so complex (e.g., ARIMA/GARCH/ML/DNN) that a full mathematical spec is infeasible. Prefer parser‑friendly constructs first (comprehensions + sum/min/max/any/all, simple math.*, scalar per‑element helpers).
652
-
653
- - Approach 1 — PascalCase (Uninterpreted Function)
654
- - What: a normal spec function that the parser represents as an uninterpreted SymPy function; prints clearly and composes symbolically.
655
- - When: you need a symbolic placeholder; runtime can be pure‑Python or evaluation isn’t required in tests. Good for things like ArgMaxIndex(values).
656
- - How: define a plain function in the spec with a pure‑Python implementation and precise types; try to keep it as declarative as possible even though it is not translated to math, i.e., it still should throw exceptions or have side-effects.
657
-
658
- - Approach 2 — Spec Wrapper + MathEngine (External Packages)
659
- - What: a thin spec wrapper that delegates to a pluggable runtime engine (installed via `set_engine`). The parser treats the call as uninterpreted; runtime can use NumPy/StatsModels/etc.
660
- - When: the operation truly requires external packages or model fitting (e.g., rolling_var, GARCH/ARIMA); you need production‑grade runtime while keeping the spec parseable.
661
- - How:
662
- - Define engine contract (add to your project’s `opaque/engine.py`):
663
- - `class MathEngine(ABC):`
664
- - `@abstractmethod`
665
- - `def rolling_var(self, returns: list[float], window: int, confidence: float) -> list[float]: ...`
666
- - Add thin wrapper in an opaque module (e.g., `axis/.../opaque/statistical.py`) that simply calls `get_engine().rolling_var(...)`.
667
- - Provide/install an engine in tests/apps: `from axis_synome.spec.ima.opaque import set_engine; set_engine(ReferenceMathEngine())`.
668
-
669
- - Authoring Rules
670
- - Keep wrappers zero‑logic (no exceptions/branching); just delegate to `get_engine()`.
671
- - Use precise, stable types and consistent snake_case names.
672
- - Briefly document new opaque functions in `opaque/README.md` (purpose, signature, runtime notes).
673
-
674
670
  ## Atlas Integration
675
671
 
676
672
  - Keep Atlas UUIDs in docstrings using `:source_uuid: ...` when available. If no Atlas section applies, omit the marker entirely.
@@ -15,6 +15,11 @@ dependencies = [
15
15
  "pydantic>=2.0.0",
16
16
  ]
17
17
 
18
+ [project.optional-dependencies]
19
+ runtime = [
20
+ "numpy>=1.26",
21
+ ]
22
+
18
23
  [project.urls]
19
24
  Repository = "https://github.com/archon-research/next-gen-atlas"
20
25
  Documentation = "https://github.com/archon-research/next-gen-atlas"
@@ -28,16 +33,17 @@ include = ["axis_synome*"]
28
33
 
29
34
  [tool.setuptools_scm]
30
35
  root = "../.."
31
- tag_regex = '^v(?P<version>\d+\.\d+\.\d+)$'
36
+ tag_regex = '^v(?P<version>\d+\.\d+(?:\.\d+)?)'
32
37
  version_file = "src/axis_synome/_version.py"
33
38
 
34
39
  [dependency-groups]
35
40
  dev = [
36
41
  "pytest>=9.0.0",
37
42
  "flake8>=7.0.0",
38
- "ruff==0.15.11",
43
+ "ruff>=0.15",
39
44
  "ty>=0.0.16",
40
45
  "lefthook>=1.9.5",
46
+ "axis-synome[runtime]",
41
47
  ]
42
48
 
43
49
  [tool.pytest.ini_options]
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.1.0.dev16'
22
- __version_tuple__ = version_tuple = (0, 1, 0, 'dev16')
21
+ __version__ = version = '0.1.0.dev27'
22
+ __version_tuple__ = version_tuple = (0, 1, 0, 'dev27')
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -0,0 +1,93 @@
1
+ import os
2
+ from abc import ABC, abstractmethod
3
+ from collections.abc import Sequence
4
+ from importlib import import_module, metadata
5
+
6
+ ENV_VAR = "AXIS_SYNOME_SPEC_RUNTIME"
7
+ ENTRY_POINT_GROUP = "axis_synome.spec_runtimes"
8
+
9
+
10
+ class SpecRuntime(ABC):
11
+ """Internal abstraction backing :mod:`axis_synome.spec_support.runtime`.
12
+
13
+ Spec authors should call the free functions in
14
+ ``axis_synome.spec_support.runtime.<domain>`` (e.g. ``runtime.math``)
15
+ rather than interacting with this class directly. Custom
16
+ implementations are selected at process start via the
17
+ ``AXIS_SYNOME_SPEC_RUNTIME`` env var or an
18
+ ``axis_synome.spec_runtimes`` entry point — see
19
+ :func:`_resolve_default_runtime`.
20
+ """
21
+
22
+ @abstractmethod
23
+ def linear_interp(
24
+ self,
25
+ x: Sequence[float],
26
+ xp: Sequence[float],
27
+ fp: Sequence[float],
28
+ ) -> list[float]:
29
+ """One-dimensional linear interpolation.
30
+
31
+ Mirrors ``numpy.interp`` semantics: for each value in ``x``, return the
32
+ linearly-interpolated value from the ``(xp, fp)`` table. ``xp`` must be
33
+ increasing; values of ``x`` outside ``[xp[0], xp[-1]]`` are clamped to
34
+ the corresponding boundary value of ``fp``.
35
+ """
36
+
37
+
38
+ def _instantiate(target: object) -> "SpecRuntime":
39
+ instance = target() if isinstance(target, type) else target
40
+ if not isinstance(instance, SpecRuntime):
41
+ raise TypeError(f"Resolved object {target!r} is not a SpecRuntime instance or subclass.")
42
+ return instance
43
+
44
+
45
+ def _load_from_env() -> "SpecRuntime | None":
46
+ spec = os.environ.get(ENV_VAR)
47
+ if not spec:
48
+ return None
49
+ if ":" not in spec:
50
+ raise RuntimeError(f"Invalid {ENV_VAR}={spec!r}: expected 'module.path:AttrName'.")
51
+ module_path, attr = spec.split(":", 1)
52
+ module = import_module(module_path)
53
+ return _instantiate(getattr(module, attr))
54
+
55
+
56
+ def _load_from_entry_points() -> "SpecRuntime | None":
57
+ eps = list(metadata.entry_points(group=ENTRY_POINT_GROUP))
58
+ if not eps:
59
+ return None
60
+ if len(eps) > 1:
61
+ names = sorted(ep.name for ep in eps)
62
+ raise RuntimeError(
63
+ f"Multiple '{ENTRY_POINT_GROUP}' entry points registered: {names}. "
64
+ f"Set {ENV_VAR}=module:Class to disambiguate."
65
+ )
66
+ return _instantiate(eps[0].load())
67
+
68
+
69
+ def _load_reference() -> "SpecRuntime | None":
70
+ try:
71
+ from axis_synome.spec_support.runtime.reference import ReferenceSpecRuntime
72
+ except ImportError:
73
+ return None
74
+ return ReferenceSpecRuntime()
75
+
76
+
77
+ def _resolve_default_runtime() -> "SpecRuntime":
78
+ for loader in (_load_from_env, _load_from_entry_points, _load_reference):
79
+ runtime = loader()
80
+ if runtime is not None:
81
+ return runtime
82
+ raise ImportError(
83
+ f"No SpecRuntime available. Install with `pip install axis-synome[runtime]`, "
84
+ f"set {ENV_VAR}=module:Class, or register an entry point in group "
85
+ f"'{ENTRY_POINT_GROUP}'."
86
+ )
87
+
88
+
89
+ _runtime: SpecRuntime = _resolve_default_runtime()
90
+
91
+
92
+ def get_runtime() -> SpecRuntime:
93
+ return _runtime
@@ -0,0 +1,32 @@
1
+ """Math operations available to spec authors.
2
+
3
+ This is the preferred API: call these free functions directly
4
+ (e.g. ``math.linear_interp(...)``) rather than going through the
5
+ underlying :class:`~axis_synome.spec_support.runtime.base.SpecRuntime`.
6
+
7
+ When ``axis-synome`` is installed with the ``[runtime]`` extra, calls are
8
+ served by ``ReferenceSpecRuntime`` (numpy-backed) by default. To install
9
+ a custom runtime, set ``AXIS_SYNOME_SPEC_RUNTIME=module:Class`` in the
10
+ environment or declare an ``axis_synome.spec_runtimes`` entry point in
11
+ the consuming package's ``pyproject.toml`` — both are resolved once at
12
+ import time.
13
+ """
14
+
15
+ from collections.abc import Sequence
16
+
17
+ from axis_synome.spec_support.runtime.base import get_runtime
18
+
19
+
20
+ def linear_interp(
21
+ x: Sequence[float],
22
+ xp: Sequence[float],
23
+ fp: Sequence[float],
24
+ ) -> list[float]:
25
+ """One-dimensional linear interpolation.
26
+
27
+ Mirrors ``numpy.interp`` semantics: for each value in ``x``, return the
28
+ linearly-interpolated value from the ``(xp, fp)`` table. ``xp`` must be
29
+ increasing; values of ``x`` outside ``[xp[0], xp[-1]]`` are clamped to
30
+ the corresponding boundary value of ``fp``.
31
+ """
32
+ return get_runtime().linear_interp(x, xp, fp)
@@ -0,0 +1,20 @@
1
+ from collections.abc import Sequence
2
+
3
+ import numpy as np
4
+
5
+ from axis_synome.spec_support.runtime.base import SpecRuntime
6
+
7
+
8
+ class ReferenceSpecRuntime(SpecRuntime):
9
+ """numpy-backed reference implementation of SpecRuntime.
10
+
11
+ Requires the ``runtime`` extra: ``pip install axis-synome[runtime]``.
12
+ """
13
+
14
+ def linear_interp(
15
+ self,
16
+ x: Sequence[float],
17
+ xp: Sequence[float],
18
+ fp: Sequence[float],
19
+ ) -> list[float]:
20
+ return np.interp(x, xp, fp).tolist()
@@ -30,6 +30,10 @@ class SpecChecker(ast.NodeVisitor):
30
30
  self._in_function = False
31
31
  self._module_imported_names: set[str] = set() # Track imports for shadowing detection
32
32
  self._function_assigned_names: set[str] = set() # Track assignments for shadowing detection
33
+ # Local names bound to axis_synome.spec_support.runtime submodules
34
+ # (e.g. spec_support.runtime.math). Calls on these bypass the curated
35
+ # stdlib math-function allowlist.
36
+ self._opaque_module_names: set[str] = set()
33
37
 
34
38
  def _error(self, node: ast.AST, msg: str) -> None:
35
39
  lineno = getattr(node, "lineno", 0)
@@ -149,6 +153,23 @@ class SpecChecker(ast.NodeVisitor):
149
153
  top_level = module.split(".")[0]
150
154
  if top_level not in ALLOWED_FROM_IMPORT_PREFIXES:
151
155
  self._error(node, f'import from "{module}" is not allowed')
156
+ return
157
+ if module == "axis_synome.spec_support.runtime" or module.startswith(
158
+ "axis_synome.spec_support.runtime."
159
+ ):
160
+ for alias in node.names:
161
+ self._opaque_module_names.add(alias.asname or alias.name)
162
+ # Restrict math imports to the allowed function names only.
163
+ if top_level == "math":
164
+ for alias in node.names:
165
+ if alias.name == "*":
166
+ self._error(node, 'wildcard import from "math" is not allowed')
167
+ elif alias.name not in ALLOWED_MATH_FUNCTION_NAMES:
168
+ self._error(
169
+ node,
170
+ f'"{alias.name}" is not an allowed math import; '
171
+ f"permitted: {sorted(ALLOWED_MATH_FUNCTION_NAMES)}",
172
+ )
152
173
 
153
174
  # ------------------------------------------------------------------
154
175
  # Names
@@ -181,6 +202,10 @@ class SpecChecker(ast.NodeVisitor):
181
202
  and isinstance(func.value, ast.Name)
182
203
  and func.value.id in ALLOWED_MODULE_CALL_PREFIXES
183
204
  ):
205
+ # Calls on axis_synome.spec_support.runtime modules (e.g. runtime.math)
206
+ # are uninterpreted and not subject to the stdlib math allowlist.
207
+ if func.value.id in self._opaque_module_names:
208
+ return
184
209
  if func.attr not in ALLOWED_MATH_FUNCTION_NAMES:
185
210
  self._error(
186
211
  node,
@@ -214,9 +239,6 @@ class SpecChecker(ast.NodeVisitor):
214
239
  def visit_Lambda(self, node: ast.Lambda) -> None:
215
240
  self._error(node, "lambda expressions are not allowed")
216
241
 
217
- def visit_ListComp(self, node: ast.ListComp) -> None:
218
- self._error(node, "list comprehensions are not allowed (use generator expressions)")
219
-
220
242
  def visit_DictComp(self, node: ast.DictComp) -> None:
221
243
  self._error(node, "dict comprehensions are not allowed")
222
244
 
@@ -262,12 +284,20 @@ class SpecChecker(ast.NodeVisitor):
262
284
  return
263
285
  # Only @dataclass or @validated_dataclass decorated classes are allowed.
264
286
  allowed_dataclass_decorators = {"dataclass", "validated_dataclass"}
287
+
288
+ def _decorator_name(d: ast.expr) -> str | None:
289
+ if isinstance(d, ast.Name):
290
+ return d.id
291
+ if isinstance(d, ast.Attribute):
292
+ return d.attr
293
+ if isinstance(d, ast.Call):
294
+ return _decorator_name(d.func)
295
+ return None
296
+
265
297
  if not any(
266
- (isinstance(d, ast.Name) and d.id in allowed_dataclass_decorators)
267
- or (isinstance(d, ast.Attribute) and d.attr in allowed_dataclass_decorators)
268
- for d in node.decorator_list
298
+ _decorator_name(d) in allowed_dataclass_decorators for d in node.decorator_list
269
299
  ):
270
- self._error(node, "only @dataclass classes are allowed")
300
+ self._error(node, "only @dataclass or @validated_dataclass classes are allowed")
271
301
  return
272
302
  # Validate the class body: only annotated assignments and docstrings.
273
303
  for stmt in node.body:
@@ -64,6 +64,7 @@ ALLOWED_FROM_IMPORT_PREFIXES: frozenset[str] = frozenset({
64
64
  "axis_synome",
65
65
  "dataclasses",
66
66
  "enum",
67
+ "math",
67
68
  "pydantic",
68
69
  "synome",
69
70
  "typing",
@@ -92,6 +93,7 @@ ALLOWED_BUILTINS: frozenset[str] = frozenset({
92
93
 
93
94
  ALLOWED_MATH_FUNCTION_NAMES: frozenset[str] = frozenset({
94
95
  "log",
96
+ "log1p",
95
97
  "exp",
96
98
  "sqrt",
97
99
  "pow",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: axis-synome
3
- Version: 0.1.0.dev16
3
+ Version: 0.1.0.dev27
4
4
  Summary: Axis specification modules (entities, formulas, validators)
5
5
  Author-email: Archon Tech <hello@archontech.ai>
6
6
  License: MIT
@@ -10,6 +10,8 @@ Requires-Python: >=3.11
10
10
  Description-Content-Type: text/markdown
11
11
  Requires-Dist: eth-hash[pycryptodome]>=0.5.0
12
12
  Requires-Dist: pydantic>=2.0.0
13
+ Provides-Extra: runtime
14
+ Requires-Dist: numpy>=1.26; extra == "runtime"
13
15
 
14
16
  # axis-synome
15
17
 
@@ -18,6 +20,7 @@ A Python framework for defining and validating financial specifications, with a
18
20
  - **ASC Specifications** — Actively Stabilizing Capital entities (assets, tokens, networks, protocols) and formulas (collateral ratios, incentives, resting/latent ASC)
19
21
  - **Spec Validator** — Flake8 plugin for enforcing Python subset constraints on spec files
20
22
  - **Metadata & Utilities** — Support for spec documentation and validated dataclasses
23
+ - **SpecRuntime** — Pluggable runtime abstraction for opaque operations (linear interpolation, ...) that specs can call without depending on a specific implementation library
21
24
 
22
25
  ## Installation
23
26
 
@@ -27,6 +30,12 @@ Install from PyPI:
27
30
  pip install axis-synome
28
31
  ```
29
32
 
33
+ To enable the built-in `ReferenceSpecRuntime` (numpy-backed), install with the `runtime` extra:
34
+
35
+ ```bash
36
+ pip install axis-synome[runtime]
37
+ ```
38
+
30
39
  Or install from a private registry:
31
40
 
32
41
  ```bash
@@ -47,6 +56,22 @@ from axis_synome.spec.asc.entities import primes, tokens, networks, protocol_set
47
56
  from axis_synome.spec.asc.formulas import asc, asc_collateral_ratio, asc_incentive
48
57
  ```
49
58
 
59
+ ### Opaque Operations in Specs
60
+
61
+ ```python
62
+ from axis_synome.spec_support.runtime import math
63
+
64
+ values = math.linear_interp([1.5], [1.0, 2.0], [10.0, 20.0]) # -> [15.0]
65
+ ```
66
+
67
+ The active runtime is selected once at import time. Resolution order:
68
+
69
+ 1. `AXIS_SYNOME_SPEC_RUNTIME=module.path:ClassName` env var
70
+ 2. A registered `axis_synome.spec_runtimes` entry point (declared by a consumer package's `pyproject.toml`)
71
+ 3. The bundled `ReferenceSpecRuntime` (requires `axis-synome[runtime]`)
72
+
73
+ If none is available, importing `axis_synome.spec_support.runtime.math` raises `ImportError` — the runtime is required at import time, not on first call.
74
+
50
75
  ### Spec Validation
51
76
 
52
77
  ```python
@@ -37,9 +37,12 @@ src/axis_synome/spec/risk_capital/formulas/__init__.py
37
37
  src/axis_synome/spec/risk_capital/formulas/required_risk_capital.py
38
38
  src/axis_synome/spec_support/__init__.py
39
39
  src/axis_synome/spec_support/evm_address.py
40
- src/axis_synome/spec_support/metadata.py
41
40
  src/axis_synome/spec_support/validated_dataclass.py
42
41
  src/axis_synome/spec_support/validated_str.py
42
+ src/axis_synome/spec_support/runtime/__init__.py
43
+ src/axis_synome/spec_support/runtime/base.py
44
+ src/axis_synome/spec_support/runtime/math.py
45
+ src/axis_synome/spec_support/runtime/reference.py
43
46
  src/axis_synome/spec_validator/__init__.py
44
47
  src/axis_synome/spec_validator/checker.py
45
48
  src/axis_synome/spec_validator/flake8_plugin.py
@@ -50,7 +53,6 @@ tests/axis_synome/asc/mocks.py
50
53
  tests/axis_synome/asc/test_asc.py
51
54
  tests/axis_synome/asc/test_asc_collateral_ratio.py
52
55
  tests/axis_synome/asc/test_asc_incentive.py
53
- tests/axis_synome/asc/test_asc_spec_client_parity.py
54
56
  tests/axis_synome/asc/test_dab.py
55
57
  tests/axis_synome/asc/test_evm_address.py
56
58
  tests/axis_synome/asc/test_latent_asc.py
@@ -60,5 +62,9 @@ tests/axis_synome/asc/test_resting_asc.py
60
62
  tests/axis_synome/risk_capital/__init__.py
61
63
  tests/axis_synome/risk_capital/formulas/__init__.py
62
64
  tests/axis_synome/risk_capital/formulas/test_loss_given_default.py
65
+ tests/axis_synome/spec_support/__init__.py
66
+ tests/axis_synome/spec_support/runtime/__init__.py
67
+ tests/axis_synome/spec_support/runtime/test_base.py
68
+ tests/axis_synome/spec_support/runtime/test_math.py
63
69
  tests/axis_synome/spec_validator/test_checker.py
64
70
  tests/axis_synome/spec_validator/test_flake8_plugin.py
@@ -1,2 +1,5 @@
1
1
  eth-hash[pycryptodome]>=0.5.0
2
2
  pydantic>=2.0.0
3
+
4
+ [runtime]
5
+ numpy>=1.26
@@ -0,0 +1,52 @@
1
+ from collections.abc import Sequence
2
+
3
+ import pytest
4
+
5
+ from axis_synome.spec_support.runtime.base import (
6
+ ENV_VAR,
7
+ SpecRuntime,
8
+ _resolve_default_runtime,
9
+ get_runtime,
10
+ )
11
+ from axis_synome.spec_support.runtime.reference import ReferenceSpecRuntime
12
+
13
+
14
+ class _StubRuntime(SpecRuntime):
15
+ def linear_interp(
16
+ self,
17
+ x: Sequence[float],
18
+ xp: Sequence[float], # noqa: ARG002
19
+ fp: Sequence[float], # noqa: ARG002
20
+ ) -> list[float]:
21
+ return [0.0 for _ in x]
22
+
23
+
24
+ class _NotARuntime:
25
+ pass
26
+
27
+
28
+ def test_default_runtime_is_reference_runtime():
29
+ assert isinstance(get_runtime(), ReferenceSpecRuntime)
30
+
31
+
32
+ def test_spec_runtime_cannot_be_instantiated_directly():
33
+ with pytest.raises(TypeError):
34
+ SpecRuntime() # type: ignore[abstract]
35
+
36
+
37
+ def test_resolve_from_env_var(monkeypatch: pytest.MonkeyPatch):
38
+ monkeypatch.setenv(ENV_VAR, f"{__name__}:_StubRuntime")
39
+ runtime = _resolve_default_runtime()
40
+ assert isinstance(runtime, _StubRuntime)
41
+
42
+
43
+ def test_resolve_from_env_var_rejects_bad_format(monkeypatch: pytest.MonkeyPatch):
44
+ monkeypatch.setenv(ENV_VAR, "no_colon_here")
45
+ with pytest.raises(RuntimeError, match="expected 'module.path:AttrName'"):
46
+ _resolve_default_runtime()
47
+
48
+
49
+ def test_resolve_from_env_var_rejects_non_spec_runtime(monkeypatch: pytest.MonkeyPatch):
50
+ monkeypatch.setenv(ENV_VAR, f"{__name__}:_NotARuntime")
51
+ with pytest.raises(TypeError, match="not a SpecRuntime"):
52
+ _resolve_default_runtime()