axis-synome 0.1.dev184__tar.gz → 0.1.dev185__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 (62) hide show
  1. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/PKG-INFO +1 -1
  2. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/WRITING_SPECS.md +163 -114
  3. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/_version.py +2 -2
  4. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/asc/formulas/asc.py +2 -19
  5. axis_synome-0.1.dev185/src/axis_synome/spec/asc/formulas/asc_collateral_ratio.py +36 -0
  6. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/asc/formulas/asc_incentive.py +4 -12
  7. axis_synome-0.1.dev185/src/axis_synome/spec/asc/formulas/dab.py +24 -0
  8. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/asc/formulas/latent_asc.py +3 -12
  9. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/asc/formulas/ratio_latent_asc.py +8 -22
  10. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/asc/formulas/resting_asc.py +3 -10
  11. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/crypto_lending/formulas/lif.py +6 -21
  12. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/risk_capital/formulas/required_risk_capital.py +3 -11
  13. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec_validator/checker.py +31 -0
  14. axis_synome-0.1.dev184/src/axis_synome/spec/asc/formulas/asc_collateral_ratio.py +0 -53
  15. axis_synome-0.1.dev184/src/axis_synome/spec/asc/formulas/dab.py +0 -30
  16. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/.flake8 +0 -0
  17. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/.gitignore +0 -0
  18. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/README.md +0 -0
  19. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/pyproject.toml +0 -0
  20. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/__init__.py +0 -0
  21. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/__init__.py +0 -0
  22. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/asc/README.md +0 -0
  23. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/asc/entities/__init__.py +0 -0
  24. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/asc/entities/assets.py +0 -0
  25. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/asc/entities/assets_by_prime.py +0 -0
  26. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/asc/entities/networks.py +0 -0
  27. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/asc/entities/primes.py +0 -0
  28. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/asc/entities/protocol_sets.py +0 -0
  29. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/asc/entities/tokens.py +0 -0
  30. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/asc/entities/types.py +0 -0
  31. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/codegen_test/entities/agents.py +0 -0
  32. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/crypto_lending/__init__.py +0 -0
  33. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/crypto_lending/formulas/__init__.py +0 -0
  34. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/risk_capital/__init__.py +0 -0
  35. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/risk_capital/formulas/__init__.py +0 -0
  36. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec_support/__init__.py +0 -0
  37. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec_support/evm_address.py +0 -0
  38. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec_support/metadata.py +0 -0
  39. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec_support/validated_dataclass.py +0 -0
  40. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec_support/validated_str.py +0 -0
  41. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec_validator/__init__.py +0 -0
  42. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec_validator/flake8_plugin.py +0 -0
  43. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec_validator/python_subset.py +0 -0
  44. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/tests/axis_synome/asc/__init__.py +0 -0
  45. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/tests/axis_synome/asc/conftest.py +0 -0
  46. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/tests/axis_synome/asc/mocks.py +0 -0
  47. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/tests/axis_synome/asc/test_asc.py +0 -0
  48. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/tests/axis_synome/asc/test_asc_collateral_ratio.py +0 -0
  49. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/tests/axis_synome/asc/test_asc_incentive.py +0 -0
  50. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/tests/axis_synome/asc/test_asc_spec_client_parity.py +0 -0
  51. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/tests/axis_synome/asc/test_dab.py +0 -0
  52. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/tests/axis_synome/asc/test_evm_address.py +0 -0
  53. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/tests/axis_synome/asc/test_latent_asc.py +0 -0
  54. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/tests/axis_synome/asc/test_prime_agent_data_validation.py +0 -0
  55. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/tests/axis_synome/asc/test_ratio_latent_asc.py +0 -0
  56. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/tests/axis_synome/asc/test_resting_asc.py +0 -0
  57. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/tests/axis_synome/risk_capital/__init__.py +0 -0
  58. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/tests/axis_synome/risk_capital/formulas/__init__.py +0 -0
  59. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/tests/axis_synome/risk_capital/formulas/test_loss_given_default.py +0 -0
  60. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/tests/axis_synome/spec_validator/test_checker.py +0 -0
  61. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/tests/axis_synome/spec_validator/test_flake8_plugin.py +0 -0
  62. {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: axis-synome
3
- Version: 0.1.dev184
3
+ Version: 0.1.dev185
4
4
  Summary: Axis specification modules (entities, formulas, validators)
5
5
  Project-URL: Repository, https://github.com/archon-research/next-gen-atlas
6
6
  Project-URL: Documentation, https://github.com/archon-research/next-gen-atlas
@@ -1,6 +1,6 @@
1
1
  # How to Write Axis Specs
2
2
 
3
- This guide distills the principles and conventions used in `specs/src/axis`. It is expected that new specs follow these
3
+ This guide distills the principles and conventions used in `python/axis_synome/src/axis_synome/spec`. It is expected that new specs follow these
4
4
  principles as much as possible. The purpose of the principles and conventions is ensure that specs remain parseable, convertible to (SymPy-backed) mathematical expressions, and that they become easy to read and navigate.
5
5
 
6
6
  This is to facilitate the following goals, paraphrased from the [Axis Synome TechSpec](https://www.notion.so/Axis-Synome-Tech-Spec-30ab87693a5a8024ab3fe29ba5c3421c?source=copy_link):
@@ -17,10 +17,9 @@ This is to facilitate the following goals, paraphrased from the [Axis Synome Tec
17
17
  Specs encode Atlas calculation rules directly in typed Python.
18
18
  They are declarative, deterministic, side‑effect‑free computations; no I/O or mutable state.
19
19
 
20
- They are organized into the `axis` namespace which contains a submodule/subdirectory `entities/` that defines dataclasses and Enums for domain inputs and constants. Many of these correspond to or stem from Atlas abstractions (typically, A6). In addition there are submodules for different types of risk calculations. These sub-directories should
21
- generally correspond to risk calculations for specific asset types (applicable to Prime agents), but this is not a fully enforced/enforceable dichotomy. Each of these submodules contains formulas:
20
+ They are organized into the `axis_synome.spec` namespace which contains a submodule/subdirectory `entities/` that defines dataclasses and Enums for domain inputs and constants. Many of these correspond to or stem from Atlas abstractions (typically, A6). In addition there are submodules grouped by calculation concepts (Atlas‑style, e.g., capital composition, credit risk, exposure transformation/mitigation). Asset classes live in `entities/` rather than as top‑level spec organization. This structure is pragmatic and reversible. Each of these submodules contains formulas:
22
21
 
23
- - Formulas are pure functions over entities that specify computations internally.
22
+ - Formulas are pure functions over entities that specify computations.
24
23
  - Each set of formulas that corresponds to a specific risk calculation, e.g., for a specific asset class is organized into its own namespace.
25
24
  - Functions are kept small; compose into top‑level results.
26
25
  - Independently testable and extractor‑friendly.
@@ -47,27 +46,47 @@ Relations are traversable: clients and tooling can evaluate predicates across th
47
46
 
48
47
  ## Building Blocks
49
48
 
50
- ### Metadata
49
+ ### Documentation and Provenance
51
50
 
52
- Metadata objects are used to annotate types used.
51
+ - Prefer function docstrings for descriptive text. Put the human‑readable description of what a formula does into the function’s docstring. Docstrings are code‑adjacent, IDE/hover friendly, and can be used to generate documentation.
52
+ - As functions are also math formulas and documentation from docstrs will also be used for the mathematical exposition, avoid Python specific terminology.
53
+ - Avoid duplicating long‑form descriptions in metadata.
54
+ - Put Atlas provenance in docstrings using reST-style fields such as `:source_uuid: ...`.
55
+ - In `python/axis_synome/src/**/formulas/*.py`, public functions are treated as formulas by convention, so plain return types like `-> float` or `-> bool` are preferred.
56
+ - Outside formula modules, docstring fields such as `:source_uuid:` provide explicit opt-in for formula discovery.
57
+ - For constants and parameters, use `Final[...]` and trailing string literal docstrings.
58
+ - Avoid per‑formula READMEs. Prefer generating documentation from docstrings to keep content in sync with code.
53
59
 
54
- Generally, Metadata is used to provide additional information about a parameter (variable), formula (function), an entity type (dataclass), or a concrete entity (an instance of a dataclass or Enum).
60
+ Examples:
55
61
 
56
62
  ```python
57
- (
58
- Metadata(
59
- description="Maximum fraction of ASC that may be Latent",
60
- source="Atlas A3.2",
61
- ),
62
- )
63
- ```
63
+ def asc(prime: EligiblePrimeAgentASC, positions: list[Position]) -> float:
64
+ """
65
+ Total Actively Stabilizing Capital for a prime.
66
+
67
+ Sum of resting and latent ASC for a given prime over its positions.
68
+
69
+ :source_uuid: 62495dee-8d2a-45d4-87c4-01150e3db3c8
70
+ """
71
+ ...
72
+
64
73
 
65
- TODO: Describe atlas schema / dir structure usage
74
+ # If no corresponding Atlas section exists, just omit the marker and keep the docstring:
75
+
76
+
77
+ def asc(prime: EligiblePrimeAgentASC, positions: list[Position]) -> float:
78
+ """
79
+ Total Actively Stabilizing Capital for a prime.
80
+
81
+ Sum of resting and latent ASC for a given prime over its positions.
82
+ """
83
+ ...
84
+ ```
66
85
 
67
86
  ### Parameters
68
87
 
69
- - Use `Final[Annotated[T, Metadata(...)] ]` for governance‑set constants.
70
- - Parameter names and metadata (`formula_id`, `description`, `source`) provide machine‑readable identities used in the graph, audits, and codegen.
88
+ - Use `Final[T]` for governance‑set constants.
89
+ - Parameter names plus structured docstring markers like `:source_uuid:` provide machine‑readable identities used in the graph, audits, and codegen. Place human‑readable descriptions in trailing constant docstrings.
71
90
 
72
91
  ### Units
73
92
 
@@ -81,25 +100,38 @@ For instance, a float-compatible `usd` type may be introduced (this is used in s
81
100
 
82
101
  ### Parameters
83
102
 
84
- - Represent global governance parameters as module‑level, annotated constants.
85
- - Use `typing.Final` for immutability and `typing.Annotated` carrying `Metadata` for description/Atlas reference.
103
+ - Represent global parameters as module‑level, annotated constants.
104
+ - Use `typing.Final` as the constant marker and place descriptive text and Atlas provenance in the trailing string literal docstring.
86
105
  - When parameters vary per entity (e.g., per Prime), model them as fields on the entity dataclass with validation (e.g., `pydantic.dataclasses.dataclass` + `Field`).
106
+ - If a constant has no relevant Atlas section, just omit the `:source_uuid:` marker and provide the human description as the trailing string literal docstring.
87
107
 
88
108
  Example:
89
109
 
90
110
  ```python
91
- from typing import Annotated, Final
92
- from axis_synome.spec_support.metadata import Metadata
93
-
94
- MAX_LATENT_ASC: Final[
95
- Annotated[
96
- usd, # usd is a floating point value compatible with Python float
97
- Metadata(
98
- description="Maximum fraction of ASC that may be Latent",
99
- source="Sky Atlas — ASC Cap",
100
- ),
101
- ]
102
- ] = 0.25
111
+ from typing import Final
112
+
113
+ MAX_LATENT_ASC: Final[usd] = 0.25 # usd is a floating point value compatible with Python float
114
+ """Maximum latent ASC fraction.
115
+
116
+ :source_uuid: 5e300cdb-b221-4b6f-9c4a-11502133a1f9
117
+ """
118
+ ```
119
+
120
+ Constant descriptions (docstrings)
121
+
122
+ - For constants, place the human‑readable description as a bare string literal immediately following the assignment. The parser treats this trailing string as the constant’s “docstring” and extracts it into the graph.
123
+ - Keep `source_uuid` and other structured fields in the trailing docstring using markers such as `:source_uuid: ...`.
124
+
125
+ Example:
126
+
127
+ ```python
128
+ from typing import Final
129
+
130
+ DAB_REQUIRED_PCT: Final[float] = 0.25
131
+ """Required DAB as a fraction of total ASC.
132
+
133
+ :source_uuid: 1e129119-a2ce-4978-b235-c50f2a1c5e2e
134
+ """
103
135
  ```
104
136
 
105
137
  ---
@@ -137,10 +169,16 @@ class Position:
137
169
  Note that all entity fields are typed to a closed set, the set of allowed values are in each case
138
170
  a fixed set instiantiated objects. `value_usd` is additionally restricted with an annotation ensuring that only positive values are supplied.
139
171
 
172
+ #### Entities Layout (Formula‑Local vs Common)
173
+
174
+ - Start formula‑local: keep entities within each formula’s package initially (`axis/<formula>/entities/`).
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
+ - Don’t add empty common folders: only create and populate shared folders when you actually have shared entities.
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.
140
178
  ### Formulas
141
179
 
142
180
  - 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)
143
- - They can and should be annotated, i.e., the return value is annotated with a `Metadata` object.
181
+ - In formula modules, prefer plain return types and put structured provenance such as `source_uuid` in the docstring. Outside formula modules, docstring fields such as `:source_uuid:` provide explicit opt-in.
144
182
  - They accept closed‑world Enums to encode eligibility and preconditions; use union types for overlapping eligible sets when needed.
145
183
  - Compose helpers; keep inclusion/exclusion criteria explicit in docstrings.
146
184
 
@@ -206,6 +244,7 @@ def asc(prime: EligiblePrimeAgentASC, positions: list[Position]) -> float:
206
244
  """
207
245
  Total Actively Stabilizing Capital for a prime.
208
246
 
247
+ Computes the sum of resting and latent ASC for a given prime over its positions.
209
248
  """
210
249
  # Enum values are concrete PrimeAgent instances
211
250
  resting_asc_usd: float = resting_asc(positions)
@@ -219,14 +258,8 @@ Each of those assignments induces a formula definition, `formula_id(resting_asc_
219
258
 
220
259
  Similarly, the return statement induces a formula definition, `formula_id(asc) = formula_id(resting_asc_usd) + formula_id(latent_asc_usd)`.
221
260
 
222
- In the example above, the function just returns a `float`. Whenever possible we should add an
223
- annotation that associates metadata with the function, i.e.,
224
-
225
- ```python
226
- def asc(prime: EligiblePrimeAgentASC, positions: list[Position]) -> Annotated[float, Metadata(...)]:
227
- ):
228
- ...
229
- ```
261
+ In formula modules, the function can just return a plain scalar type and carry provenance in the
262
+ docstring, e.g. `def asc(...) -> float: ...`.
230
263
 
231
264
  #### Boolean functions as constraints
232
265
 
@@ -236,7 +269,9 @@ Boolean constraint functions that take closed‑world sets (e.g., `EligiblePrime
236
269
  Specification functions that take one or more closed-world Entity argument are constraints that should hold true for **all** combinations of the matching inputs to the functions.
237
270
 
238
271
  ```python
239
- def compliant_prime(prime: EligiblePrimeAgentASC) -> Annotated[bool, Metadata(...)]: ...
272
+ def compliant_prime(
273
+ prime: EligiblePrimeAgentASC,
274
+ ) -> bool: ...
240
275
  ```
241
276
 
242
277
  Multi-input constraints are evaluated across the Cartesian product of their closed-world domains:
@@ -247,32 +282,43 @@ For instance, we might have a constraint that two distinct primes do not share a
247
282
  def portfolio_diversification(
248
283
  prime_a: PrimeAgentAll,
249
284
  prime_b: PrimeAgentAll,
250
- ) -> Annotated[bool, Metadata("Distinct primes do not concentrate identical assets", "Atlas Ref")]:
251
- """Verifies that no two distinct primes hold the same asset above a threshold."""
285
+ ) -> bool:
286
+ """Verifies that no two distinct primes hold the same asset above a threshold.
287
+
288
+ :source_uuid: 62495dee-8d2a-45d4-87c4-01150e3db3c8
289
+ """
252
290
  return prime_a == prime_b or not (asset in prime_a.assets and asset in prime_b.assets)
253
291
  ```
254
292
 
255
293
  Assuming that `PrimeAgentAll` is an Enum consisting of prime agent instances, tooling evaluates this constraint across the Cartesian product `PrimeAgentAll × PrimeAgentAll`, ensuring the predicate holds for all combinations where prime_a ≠ prime_b.
256
294
 
257
295
 
296
+ ### Testing
297
+
298
+ - Happy‑path tests only (internal initially): provide one simple, end‑to‑end test per formula demonstrating basic execution flow. Avoid asserting on highly specific numerical guarantees unless essential; prefer sanity checks that exercise the graph and opaque boundaries.
299
+ - 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
+ - Declarative first: specs are declarative and amenable to static verification; tests complement by documenting usage and catching regressions in opaque integrations.
301
+
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
+
258
309
  ## Starter Template
259
310
 
260
311
  Start a new spec module with (simplified example; in real modules, entities typically live in their own package folder):
261
312
 
262
313
  ```python
263
314
  # parameters.py
264
- from typing import Annotated, Final
265
- from axis_synome.spec_support.metadata import Metadata
266
-
267
- THRESHOLD: Final[
268
- Annotated[
269
- float,
270
- Metadata(
271
- description="Breach threshold in USD",
272
- source="Atlas Ref",
273
- ),
274
- ]
275
- ] = 1_000_000.0
315
+ from typing import Final
316
+
317
+ THRESHOLD: Final[float] = 1_000_000.0
318
+ """Example threshold constant.
319
+
320
+ :source_uuid: 62495dee-8d2a-45d4-87c4-01150e3db3c8
321
+ """
276
322
  ```
277
323
 
278
324
  ```python
@@ -300,8 +346,6 @@ class ItemSet(Enum):
300
346
 
301
347
  ```python
302
348
  # formulas.py
303
- from typing import Annotated
304
- from axis_synome.spec_support.metadata import Metadata
305
349
  from .parameters import THRESHOLD
306
350
  from .entities.items import ItemSet, EligibleOwner
307
351
 
@@ -326,14 +370,11 @@ def owner_item_value(owner: EligibleOwner, item: ItemSet) -> float:
326
370
 
327
371
  def total_value(
328
372
  owner: EligibleOwner,
329
- ) -> Annotated[
330
- float,
331
- Metadata(
332
- formula_id="formula_total_value",
333
- description="Total USD value from closed‑world item set",
334
- source="Atlas Ref",
335
- ),
336
- ]:
373
+ ) -> float:
374
+ """Total value for an eligible owner.
375
+
376
+ :source_uuid: DOC-EXAMPLE-UUID
377
+ """
337
378
  # Computes over the closed‑world ItemSet domain
338
379
  return sum(owner_item_value(owner, it) for it in ItemSet)
339
380
 
@@ -350,7 +391,7 @@ def threshold_satisfied(owner: EligibleOwner) -> bool:
350
391
  - Aggregators: `sum`, `min`, `max`, `any`, `all` over generator expressions.
351
392
  - Generator expressions and simple `range(...)` where needed.
352
393
  - Selected `math.*` (e.g., `log`, `exp`, `sqrt`, `pow`).
353
- - Imports from `axis`, `synome`, `typing`, `dataclasses`, `enum`, `pydantic`, and bare `math`.
394
+ - Imports from `axis_synome.spec`, `synome`, `typing`, `dataclasses`, `enum`, `pydantic`, and bare `math`.
354
395
 
355
396
  - Avoid / Disallowed
356
397
  - Mutation, I/O, networking, side effects; exceptions and `try/except`.
@@ -398,7 +439,7 @@ The parser extracts formulas from typed Python by walking the function AST and l
398
439
  - Dict literals and dict comprehensions are disallowed in formulas. Set comprehensions are not currently supported by the parser; if used, they will not be converted and may fallback to uninterpreted forms.
399
440
  - Exceptions and side effects are out of scope by design.
400
441
 
401
- Metadata extraction: the parser recognizes `formula_id`, `description`, and `source` via `Annotated[..., Metadata(...)]` on return types. Units are not required; document units in docstrings if needed.
442
+ Formula extraction: in `python/axis_synome/src/**/formulas/*.py`, the parser treats public functions as formulas by convention and reads `source_uuid` from docstring fields such as `:source_uuid: ...`. Outside formula modules, those docstring fields remain the explicit opt-in. Prefer putting descriptions in docstrings; units are not required and can be documented there as needed.
402
443
 
403
444
  ---
404
445
 
@@ -408,7 +449,7 @@ Use the Makefile targets to run checks consistently:
408
449
 
409
450
  - Lint (ruff): `make lint` → `uv run ruff check .`
410
451
  - Format (ruff): `make format` or `make format-check`
411
- - Specs lint (flake8 in specs): `make lint-specs` (runs from `specs/`)
452
+ - Specs lint (flake8 in specs): `make lint-specs` (runs from `python/axis_synome/`)
412
453
  - Type check (Ty): `make typecheck` (runs `ty check` for `src/` and `explorations/`)
413
454
  - All checks: `make check-all`
414
455
 
@@ -425,19 +466,19 @@ Use the Makefile targets to run checks consistently:
425
466
  - Run: `make lint` for checks, `make fix` to auto-fix, `make format` for formatting.
426
467
 
427
468
  - Flake8 (spec-subset validator)
428
- - In `specs/`, Flake8 runs a custom plugin that emits AXS00x errors when a file uses constructs outside the allowed subset defined in `axis_common.python_subset`.
469
+ - In `python/axis_synome/`, Flake8 runs a custom plugin that emits AXS00x errors when a file uses constructs outside the allowed subset defined in `axis_synome.spec_validator.python_subset`.
429
470
  - Examples of disallowed patterns: nested functions in formulas, methods in dataclasses/Enums, non-whitelisted imports, f-strings, dict/set comprehensions, `try/except`, `yield`, walrus `:=`, unapproved builtins.
430
471
  - Only spec files are validated by the plugin; editor integrations surface these as AXS diagnostics.
431
- - Run: `make lint-specs` (executes from `specs/`).
472
+ - Run: `make lint-specs` (executes from `python/axis_synome/`).
432
473
 
433
474
  - Ty (static type checking)
434
475
  - Validates function signatures, dataclass fields, and return types across runtime code and specs. Types must line up; otherwise the graph edges inferred from signatures become unsound.
435
- - Encourages narrow, precise types on inputs/outputs (e.g., closed-world Enums, `Final`, `Annotated[..., Metadata(...)]`).
476
+ - Encourages narrow, precise types on inputs/outputs (e.g., closed-world Enums, `Final`, and precise scalar return types).
436
477
  - Run: `make typecheck`.
437
478
 
438
479
  ### How Checks Align With The Parser
439
480
 
440
- - Single source of truth for the subset. The same allowlist powering the Flake8 plugin (`axis_common.python_subset`) is used by the parser when walking function ASTs to produce SymPy. If Flake8 passes, the parser will not encounter unexpected statement/expression kinds.
481
+ - Single source of truth for the subset. The same allowlist powering the Flake8 plugin (`axis_synome.spec_validator.python_subset`) is used by the parser when walking function ASTs to produce SymPy. If Flake8 passes, the parser will not encounter unexpected statement/expression kinds.
441
482
  - Safe constructs are lowered deterministically. Comprehensions, boolean ops, comparisons, `sum/min/max`, selected `math.*`, and simple conditionals become SymPy expressions or uninterpreted functions with clear bounds.
442
483
  - Disallowed constructs block extraction early. If you see an `AXS001 ... is not allowed` error, change the code to an allowed equivalent (e.g., use a generator expression and `sum(...)` instead of a loop; prefer ternary `x if c else y` over complex branching inside expressions).
443
484
 
@@ -445,14 +486,14 @@ Use the Makefile targets to run checks consistently:
445
486
 
446
487
  - Signatures define edges. A formula like `def asc(prime: EligiblePrimeAgentASC, positions: list[Position]) -> float` induces edges from each `EligiblePrimeAgentASC` member to `asc`, and from any sub-formulas it calls to `asc`.
447
488
  - Closed-world Enums enable universal and product quantification. Boolean constraints over Enums are checked over all members or Cartesian products without execution, thanks to types.
448
- - Entities and parameters carry semantics. Dataclass fields typed to domain-specific classes and parameters typed as `Final[Annotated[T, Metadata(...)] ]` provide machine-readable identities (`formula_id`, `description`, `source`) used in graph labeling, audits, and downstream codegen.
489
+ - Entities and parameters carry semantics. Dataclass fields typed to domain-specific classes and typed constants such as `Final[T]` provide machine-readable identities; `source_uuid` comes from docstring markers such as `:source_uuid:` and is used in graph labeling, audits, and downstream codegen. Place long‑form descriptions in docstrings.
449
490
  - Type checking safeguards graph integrity. If you widen a type (e.g., `Any` or a loose `str`) you reduce the graph’s precision; Ty will highlight mismatches so specs remain traversable and analyzable.
450
491
 
451
492
  ### Typical Violations And Fixes
452
493
 
453
494
  - For-loops inside formulas → Replace with generator expressions plus `sum(...)`, `any(...)`, or `all(...)`.
454
495
  - Dict/set comprehensions → Move structure-building to entities or use lists/tuples; keep formula bodies expression-centric.
455
- - Unapproved builtins/imports → Use only builtins in the allowlist and imports under permitted prefixes (`axis`, `typing`, `dataclasses`, `enum`, `pydantic`, `synome`, or `math`).
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`).
456
497
  - Methods in dataclasses/Enums → Move logic into free functions; limit classes to annotated fields (dataclasses) or constant members (Enums).
457
498
  - Missing/loose types → Add precise argument and return annotations; prefer closed-world Enums where policy implies eligibility.
458
499
 
@@ -483,12 +524,11 @@ These hypothetical examples show how mathematical expression/equations can be co
483
524
  $\mathrm{PortfolioUSD} = H_{\mathrm{USDC}} + H_{\mathrm{USDT}} + H_{\mathrm{USDS}} + H_{\mathrm{DAI}}$
484
525
 
485
526
  ```python
486
- def portfolio_usd(
487
- h_usdc: float, h_usdt: float, h_usds: float, h_dai: float
488
- ) -> Annotated[
489
- float,
490
- Metadata(description="Total USD across stablecoin holdings", source="Example"),
491
- ]:
527
+ def portfolio_usd(h_usdc: float, h_usdt: float, h_usds: float, h_dai: float) -> float:
528
+ """Total USD across stablecoin holdings.
529
+
530
+ :source_uuid: DOC-EXAMPLE-UUID
531
+ """
492
532
  holdings = [("USDC", h_usdc), ("USDT", h_usdt), ("USDS", h_usds), ("DAI", h_dai)]
493
533
  total: float = sum(value for _, value in holdings)
494
534
  return total
@@ -501,23 +541,21 @@ $\mathrm{margin} = \dfrac{\mathrm{assetsUSD}}{\mathrm{debtUSD}} - \mathrm{minRat
501
541
  $\mathrm{collateral\_ratio\_satisfied} \iff \mathrm{margin} \ge 0$
502
542
 
503
543
  ```python
504
- def collateral_ratio_margin(
505
- assets_usd: float, debt_usd: float, min_ratio: float
506
- ) -> Annotated[
507
- float,
508
- Metadata(description="Collateral ratio minus minimum", source="Example"),
509
- ]:
544
+ def collateral_ratio_margin(assets_usd: float, debt_usd: float, min_ratio: float) -> float:
545
+ """Collateral ratio minus minimum.
546
+
547
+ :source_uuid: DOC-EXAMPLE-UUID
548
+ """
510
549
  ratio: float = assets_usd / debt_usd
511
550
  margin: float = ratio - min_ratio
512
551
  return margin
513
552
 
514
553
 
515
- def collateral_ratio_satisfied(
516
- assets_usd: float, debt_usd: float, min_ratio: float
517
- ) -> Annotated[
518
- bool,
519
- Metadata(description="Collateral ratio >= minimum", source="Example"),
520
- ]:
554
+ def collateral_ratio_satisfied(assets_usd: float, debt_usd: float, min_ratio: float) -> bool:
555
+ """True when collateral ratio meets or exceeds the minimum.
556
+
557
+ :source_uuid: DOC-EXAMPLE-UUID
558
+ """
521
559
  return collateral_ratio_margin(assets_usd, debt_usd, min_ratio) >= 0.0
522
560
  ```
523
561
 
@@ -531,10 +569,11 @@ $\mathrm{haircut}(\sigma_{30\mathrm{d}}) = \begin{cases}
531
569
  ```python
532
570
  def volatility_haircut(
533
571
  vol_30d: float,
534
- ) -> Annotated[
535
- float,
536
- Metadata(description="Haircut by 30d volatility threshold", source="Example"),
537
- ]:
572
+ ) -> float:
573
+ """Haircut by 30d volatility threshold.
574
+
575
+ :source_uuid: DOC-EXAMPLE-UUID
576
+ """
538
577
  return 0.02 if vol_30d < 0.2 else 0.05
539
578
  ```
540
579
 
@@ -547,10 +586,11 @@ $w_{\mathrm{PSM}}=U_{\mathrm{PSM}}/T,\quad w_{\mathrm{Curve}}=U_{\mathrm{Curve}}
547
586
  $\max\{w_{\mathrm{PSM}}, w_{\mathrm{Curve}}, w_{\mathrm{Uni}}\} \le \mathrm{cap}$
548
587
 
549
588
  ```python
550
- def max_venue_concentration(psm_usd: float, curve_usd: float, uni_usd: float, cap: float) -> Annotated[
551
- bool,
552
- Metadata(description="Max venue share <= cap", source="Example"),
553
- ]:
589
+ def max_venue_concentration(psm_usd: float, curve_usd: float, uni_usd: float, cap: float) -> bool:
590
+ """True when the maximum venue share is at or below the cap.
591
+
592
+ :source_uuid: DOC-EXAMPLE-UUID
593
+ """
554
594
  total: float = psm_usd + curve_usd + uni_usd
555
595
  w_psm: float = psm_usd / total
556
596
  w_curve: float = curve_usd / total
@@ -564,9 +604,9 @@ def max_venue_concentration(psm_usd: float, curve_usd: float, uni_usd: float, ca
564
604
  - Use types (dataclasses, Enums, precise signatures) to encode policy and traversable relations.
565
605
 
566
606
  - Minimal template
567
- - Parameters: `Final[Annotated[T, Metadata(...)] ]` for governance‑set constants.
607
+ - Parameters: `Final[T]` for governance‑set constants, with provenance in trailing docstrings.
568
608
  - Entities: frozen dataclasses and closed‑world Enums of concrete instances.
569
- - Formulas: pure functions over these types with precise return annotations (prefer `Annotated[..., Metadata(...)]`).
609
+ - Formulas: pure functions over these types with precise return types; put provenance in docstrings.
570
610
 
571
611
  - Do / Don’t (subset cheat‑sheet)
572
612
  - Do: use generator expressions + `sum/min/max/any/all`, ternary `x if c else y`, selected `math.*`, and composition of small helpers.
@@ -584,12 +624,11 @@ def max_venue_concentration(psm_usd: float, curve_usd: float, uni_usd: float, ca
584
624
  $\mathrm{PV} = \sum_{i=0}^{n-1} \dfrac{\mathrm{CF}_i}{(1+r)^{i+1}}$
585
625
 
586
626
  ```python
587
- def present_value(
588
- cashflows: list[float], r: float
589
- ) -> Annotated[
590
- float,
591
- Metadata(description="Sum CF[i]/(1+r)^(i+1) over i in [0,n)", source="Example"),
592
- ]:
627
+ def present_value(cashflows: list[float], r: float) -> float:
628
+ """Present value of discounted cash flows.
629
+
630
+ :source_uuid: DOC-EXAMPLE-UUID
631
+ """
593
632
  n: int = len(cashflows)
594
633
  return sum(cashflows[i] / (1.0 + r) ** (i + 1) for i in range(n))
595
634
  ```
@@ -599,12 +638,11 @@ def present_value(
599
638
  $\forall i \in \{0,\dots,n-1\}:\; p_i \ge \tau$
600
639
 
601
640
  ```python
602
- def all_positions_above(
603
- positions: list[float], threshold: float
604
- ) -> Annotated[
605
- bool,
606
- Metadata(description="All positions >= threshold", source="Example"),
607
- ]:
641
+ def all_positions_above(positions: list[float], threshold: float) -> bool:
642
+ """All positions above a threshold.
643
+
644
+ :source_uuid: DOC-EXAMPLE-UUID
645
+ """
608
646
  return all(p >= threshold for p in positions)
609
647
  ```
610
648
 
@@ -632,3 +670,14 @@ Use only when the operation cannot be expressed in the allowed, parser‑friendl
632
670
  - Keep wrappers zero‑logic (no exceptions/branching); just delegate to `get_engine()`.
633
671
  - Use precise, stable types and consistent snake_case names.
634
672
  - Briefly document new opaque functions in `opaque/README.md` (purpose, signature, runtime notes).
673
+
674
+ ## Atlas Integration
675
+
676
+ - Keep Atlas UUIDs in docstrings using `:source_uuid: ...` when available. If no Atlas section applies, omit the marker entirely.
677
+ - Prefer docstrings in specs as the authoritative descriptive source. Atlas Markdown pages can be “hollowed out” to minimal anchors that link back to Synome specs and reuse docstrings when possible. This approach avoids duplicate, drifting descriptions. Pending Atlas Access team alignment.
678
+
679
+ ## Versioning and Backwards Compatibility
680
+
681
+ - Early phase: limited backwards‑compatibility promises. We may iterate quickly across major versions.
682
+ - Breaking changes (e.g., moving entities from formula‑local to shared `axis/entities/`, changing import paths) require a major version bump.
683
+ - Where feasible, provide temporary re‑exports/shims to ease upgrades, but prefer clarity over long‑term indirection.
@@ -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.dev184'
22
- __version_tuple__ = version_tuple = (0, 1, 'dev184')
21
+ __version__ = version = '0.1.dev185'
22
+ __version_tuple__ = version_tuple = (0, 1, 'dev185')
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -1,31 +1,14 @@
1
- from typing import Annotated
2
-
3
1
  from axis_synome.spec.asc.entities.primes import EligiblePrimeAgentASC
4
2
  from axis_synome.spec.asc.entities.types import Position
5
3
  from axis_synome.spec.asc.formulas.latent_asc import latent_asc
6
4
  from axis_synome.spec.asc.formulas.resting_asc import resting_asc
7
- from axis_synome.spec_support.metadata import Metadata
8
-
9
- BLAH = 5
10
-
11
-
12
- """Seconds to wait before the connection times out."""
13
-
14
- "hello"
15
5
 
16
6
 
17
- def asc(
18
- prime: EligiblePrimeAgentASC, positions: list[Position]
19
- ) -> Annotated[
20
- float,
21
- Metadata(
22
- description="Total Actively Stabilizing Capital for a prime: resting ASC plus latent ASC.",
23
- source_uuid="62495dee-8d2a-45d4-87c4-01150e3db3c8",
24
- ),
25
- ]:
7
+ def asc(prime: EligiblePrimeAgentASC, positions: list[Position]) -> float:
26
8
  """
27
9
  Total Actively Stabilizing Capital for a prime.
28
10
 
11
+ :source_uuid: 62495dee-8d2a-45d4-87c4-01150e3db3c8
29
12
  """
30
13
  resting_asc_usd: float = resting_asc(positions)
31
14
  latent_asc_usd: float = latent_asc(prime, positions)
@@ -0,0 +1,36 @@
1
+ from axis_synome.spec.asc.entities.primes import EligiblePrimeAgentASC
2
+ from axis_synome.spec.asc.entities.types import Position
3
+ from axis_synome.spec.asc.formulas.latent_asc import latent_asc
4
+ from axis_synome.spec.asc.formulas.resting_asc import resting_asc
5
+
6
+
7
+ def collateral_portfolio(
8
+ positions: list[Position],
9
+ ) -> float:
10
+ """Total Collateral Portfolio value for a prime: sum of all positions.
11
+
12
+ :source_uuid: 64e1390f-68a1-43ec-87a8-8ae7b990f7ec
13
+ """
14
+ return sum(p.value_usd for p in positions)
15
+
16
+
17
+ def asc_collateral_ratio(prime: EligiblePrimeAgentASC, positions: list[Position]) -> float:
18
+ """ASC collateral ratio slack.
19
+
20
+ Returns the arithmetic result unconditionally. A negative value means
21
+ ASC is below the required minimum fraction of the Collateral Portfolio.
22
+
23
+ :source_uuid: 475fe222-9e4a-4e9d-9be6-a7a424ce02f8
24
+ """
25
+ agent = prime.value
26
+ asc_usd: float = resting_asc(positions) + latent_asc(prime, positions)
27
+ portfolio_usd: float = collateral_portfolio(positions)
28
+ return asc_usd - portfolio_usd * agent.min_pct_asc_of_collateral
29
+
30
+
31
+ def asc_collateral_ratio_satisfied(prime: EligiblePrimeAgentASC, positions: list[Position]) -> bool:
32
+ """Boolean ASC collateral ratio compliance predicate. True iff asc_collateral_ratio >= 0.
33
+
34
+ :source_uuid: 475fe222-9e4a-4e9d-9be6-a7a424ce02f8
35
+ """
36
+ return asc_collateral_ratio(prime, positions) >= 0.0
@@ -1,10 +1,7 @@
1
- from typing import Annotated
2
-
3
1
  from axis_synome.spec.asc.entities.primes import EligiblePrimeAgentASC
4
2
  from axis_synome.spec.asc.entities.types import Position
5
3
  from axis_synome.spec.asc.formulas.latent_asc import latent_asc
6
4
  from axis_synome.spec.asc.formulas.resting_asc import resting_asc
7
- from axis_synome.spec_support.metadata import Metadata
8
5
 
9
6
  # base_float and treasury_bill_rate are both expressed as params.
10
7
 
@@ -14,15 +11,10 @@ def asc_incentive(
14
11
  positions: list[Position],
15
12
  base_rate: float,
16
13
  treasury_bill_rate: float,
17
- ) -> Annotated[
18
- float,
19
- Metadata(
20
- description="ASC incentive payment: eligible ASC multiplied by the spread between base rate and treasury bill rate.",
21
- source_uuid="693330d6-9072-4054-bd61-d788537e34e8",
22
- ),
23
- ]:
24
- """
25
- ASC incentive payment
14
+ ) -> float:
15
+ """ASC incentive payment: eligible ASC times (base_rate - treasury_bill_rate).
16
+
17
+ :source_uuid: 693330d6-9072-4054-bd61-d788537e34e8
26
18
  """
27
19
  eligible_asc_usd: float = resting_asc(positions) + latent_asc(prime, positions)
28
20
  return eligible_asc_usd * (base_rate - treasury_bill_rate)
@@ -0,0 +1,24 @@
1
+ from typing import Final
2
+
3
+ from axis_synome.spec.asc.entities.primes import EligiblePrimeAgentASC
4
+ from axis_synome.spec.asc.entities.types import Position
5
+ from axis_synome.spec.asc.formulas.latent_asc import latent_asc
6
+ from axis_synome.spec.asc.formulas.resting_asc import resting_asc
7
+
8
+ DAB_REQUIRED_PCT: Final[float] = 0.25
9
+ """Required DAB as a fraction of total ASC.
10
+
11
+ :source_uuid: 1e129119-a2ce-4978-b235-c50f2a1c5e2e
12
+ """
13
+
14
+
15
+ def dab_required(
16
+ prime: EligiblePrimeAgentASC,
17
+ positions: list[Position],
18
+ ) -> float:
19
+ """Required DAB: DAB_REQUIRED_PCT of ASC.
20
+
21
+ :source_uuid: 1e129119-a2ce-4978-b235-c50f2a1c5e2e
22
+ """
23
+ asc_usd = resting_asc(positions) + latent_asc(prime, positions)
24
+ return asc_usd * DAB_REQUIRED_PCT
@@ -1,21 +1,10 @@
1
- from typing import Annotated
2
-
3
1
  from axis_synome.spec.asc.entities.primes import EligiblePrimeAgentASC
4
2
  from axis_synome.spec.asc.entities.protocol_sets import LENDING_PROTOCOLS, POOL_PROTOCOLS
5
3
  from axis_synome.spec.asc.entities.tokens import CASH_STABLECOINS
6
4
  from axis_synome.spec.asc.entities.types import Position
7
- from axis_synome.spec_support.metadata import Metadata
8
5
 
9
6
 
10
- def latent_asc(
11
- prime: EligiblePrimeAgentASC, positions: list[Position]
12
- ) -> Annotated[
13
- float,
14
- Metadata(
15
- description="Latent ASC: stabilizing capital not immediately liquid, held by a prime.",
16
- source_uuid="35ce6b38-9fc1-456e-93da-10ab1468a8bf",
17
- ),
18
- ]:
7
+ def latent_asc(prime: EligiblePrimeAgentASC, positions: list[Position]) -> float:
19
8
  """Latent ASC for a prime: which is the stabilizing capital not immediately liquid.
20
9
 
21
10
  Includes:
@@ -25,6 +14,8 @@ def latent_asc(
25
14
 
26
15
  Pool positions paired with USDS are excluded (those contribute to
27
16
  RestingASC).
17
+
18
+ :source_uuid: 35ce6b38-9fc1-456e-93da-10ab1468a8bf
28
19
  """
29
20
  agent = prime.value
30
21
  return sum(
@@ -1,25 +1,16 @@
1
- from typing import Annotated
2
-
3
1
  from axis_synome.spec.asc.entities.primes import EligiblePrimeAgentASC
4
2
  from axis_synome.spec.asc.entities.types import Position
5
3
  from axis_synome.spec.asc.formulas.latent_asc import latent_asc
6
4
  from axis_synome.spec.asc.formulas.resting_asc import resting_asc
7
- from axis_synome.spec_support.metadata import Metadata
8
5
 
9
6
 
10
- def ratio_latent_asc(
11
- prime: EligiblePrimeAgentASC, positions: list[Position]
12
- ) -> Annotated[
13
- float,
14
- Metadata(
15
- description="Latent ASC ratio slack: permitted latent cap minus actual latent ASC. Must be >= 0 for compliance.",
16
- source_uuid="5e300cdb-b221-4b6f-9c4a-11502133a1f9",
17
- ),
18
- ]:
7
+ def ratio_latent_asc(prime: EligiblePrimeAgentASC, positions: list[Position]) -> float:
19
8
  """Latent ASC ratio slack.
20
9
 
21
10
  Returns the arithmetic result unconditionally. A negative value means
22
11
  LatentASC exceeds the permitted fraction of total ASC.
12
+
13
+ :source_uuid: 5e300cdb-b221-4b6f-9c4a-11502133a1f9
23
14
  """
24
15
  agent = prime.value
25
16
  latent_usd: float = latent_asc(prime, positions)
@@ -27,14 +18,9 @@ def ratio_latent_asc(
27
18
  return total_usd * agent.max_pct_latent_asc - latent_usd
28
19
 
29
20
 
30
- def latent_asc_ratio_satisfied(
31
- prime: EligiblePrimeAgentASC, positions: list[Position]
32
- ) -> Annotated[
33
- bool,
34
- Metadata(
35
- description="True when latent ASC is within the permitted fraction of total ASC.",
36
- source_uuid="5e300cdb-b221-4b6f-9c4a-11502133a1f9",
37
- ),
38
- ]:
39
- """Boolean latent ASC ratio compliance predicate. True iff ratio_latent_asc >= 0."""
21
+ def latent_asc_ratio_satisfied(prime: EligiblePrimeAgentASC, positions: list[Position]) -> bool:
22
+ """Boolean latent ASC ratio compliance predicate. True iff ratio_latent_asc >= 0.
23
+
24
+ :source_uuid: 5e300cdb-b221-4b6f-9c4a-11502133a1f9
25
+ """
40
26
  return ratio_latent_asc(prime, positions) >= 0.0
@@ -1,5 +1,3 @@
1
- from typing import Annotated
2
-
3
1
  from axis_synome.spec.asc.entities.networks import L2_NETWORKS
4
2
  from axis_synome.spec.asc.entities.protocol_sets import (
5
3
  GUNI_PROTOCOLS,
@@ -10,18 +8,11 @@ from axis_synome.spec.asc.entities.protocol_sets import (
10
8
  )
11
9
  from axis_synome.spec.asc.entities.tokens import CASH_STABLECOINS, Token
12
10
  from axis_synome.spec.asc.entities.types import Position
13
- from axis_synome.spec_support.metadata import Metadata
14
11
 
15
12
 
16
13
  def resting_asc(
17
14
  positions: list[Position],
18
- ) -> Annotated[
19
- float,
20
- Metadata(
21
- description="Resting ASC: immediately liquid stabilizing capital held by a prime.",
22
- source_uuid="4e8cd2d1-4c74-49fd-b3fe-f8b6ccc1a79f",
23
- ),
24
- ]:
15
+ ) -> float:
25
16
  """Resting ASC for a prime: immediately liquid stabilizing capital.
26
17
 
27
18
  Includes:
@@ -31,6 +22,8 @@ def resting_asc(
31
22
  - USDC in G-UNI at eligible fee tiers (0.01% = 0.0001, 0.05% = 0.0005)
32
23
 
33
24
  All positions are assumed to belong to this prime.
25
+
26
+ :source_uuid: 4e8cd2d1-4c74-49fd-b3fe-f8b6ccc1a79f
34
27
  """
35
28
  return sum(
36
29
  p.value_usd
@@ -7,33 +7,18 @@ following the Morpho protocol specification.
7
7
  Reference: https://docs.morpho.org/learn/concepts/liquidation/
8
8
  """
9
9
 
10
- from typing import Annotated
10
+ from typing import Final
11
11
 
12
- from axis_synome.spec_support.metadata import Metadata
12
+ BETA: Final[float] = 0.3
13
+ """Weight parameter for the LLTV in the LIF denominator calculation."""
13
14
 
14
- BETA: Annotated[
15
- float,
16
- Metadata(
17
- description="Morpho liquidation curve steepness parameter",
18
- ),
19
- ] = 0.3
20
-
21
- M: Annotated[
22
- float,
23
- Metadata(
24
- description="Maximum liquidation incentive factor",
25
- ),
26
- ] = 1.15
15
+ M: Final[float] = 1.15
16
+ """Maximum liquidation incentive factor cap."""
27
17
 
28
18
 
29
19
  def lif(
30
20
  lltv: float,
31
- ) -> Annotated[
32
- float,
33
- Metadata(
34
- description="Liquidation incentive factor for a given LLTV",
35
- ),
36
- ]:
21
+ ) -> float:
37
22
  """
38
23
  Calculate the liquidation incentive factor (LIF) for a given LLTV.
39
24
 
@@ -7,22 +7,12 @@ occurs at a given slippage, following the Atlas specification.
7
7
  Reference: A.3.2.2.1.1.1.1.1.2 — Calculate Loss Given Default
8
8
  """
9
9
 
10
- from typing import Annotated
11
-
12
- from axis_synome.spec_support.metadata import Metadata
13
-
14
10
 
15
11
  def loss_given_default(
16
12
  liquidation_penalty: float,
17
13
  slippage: float,
18
14
  liquidation_threshold: float,
19
- ) -> Annotated[
20
- float,
21
- Metadata(
22
- description="Fractional loss on a collateral position at a given slippage",
23
- source_uuid="c9bd4928-d054-4e89-9a98-720c439b0db3",
24
- ),
25
- ]:
15
+ ) -> float:
26
16
  """
27
17
  Calculate the loss given default for a collateral position.
28
18
 
@@ -34,6 +24,8 @@ def loss_given_default(
34
24
  liquidation_threshold: LT, debt as fraction of collateral at liquidation [0, 1]
35
25
 
36
26
  Returns a value in [0, 1]. Zero means no loss; 1 means total loss.
27
+
28
+ :source_uuid: c9bd4928-d054-4e89-9a98-720c439b0db3
37
29
  """
38
30
  recovery: float = (1.0 - liquidation_penalty) * (1.0 - slippage) / liquidation_threshold
39
31
  return max(0.0, min(1.0, 1.0 - recovery))
@@ -49,6 +49,8 @@ class SpecChecker(ast.NodeVisitor):
49
49
  # ------------------------------------------------------------------
50
50
 
51
51
  def visit_Module(self, node: ast.Module) -> None:
52
+ # Validate trailing string literal constant docstrings where present.
53
+ self._validate_constant_docstrings(node)
52
54
  # Pre-scan for imported names for shadowing detection
53
55
  for stmt in node.body:
54
56
  if isinstance(stmt, (ast.Import, ast.ImportFrom)):
@@ -68,6 +70,35 @@ class SpecChecker(ast.NodeVisitor):
68
70
  else:
69
71
  self.visit(stmt)
70
72
 
73
+ def _validate_constant_docstrings(self, node: ast.Module) -> None:
74
+ """Validate trailing string literal docstrings for documented constants."""
75
+ body = list(getattr(node, "body", []))
76
+ for i, stmt in enumerate(body):
77
+ target: ast.Name | None = None
78
+ if isinstance(stmt, ast.AnnAssign) and isinstance(stmt.target, ast.Name):
79
+ target = stmt.target
80
+ elif (
81
+ isinstance(stmt, ast.Assign)
82
+ and len(stmt.targets) == 1
83
+ and isinstance(stmt.targets[0], ast.Name)
84
+ ):
85
+ target = stmt.targets[0]
86
+
87
+ if target is None or not target.id.isupper():
88
+ continue
89
+
90
+ if i + 1 < len(body):
91
+ nxt = body[i + 1]
92
+ if isinstance(nxt, ast.Expr) and isinstance(
93
+ getattr(nxt, "value", None), ast.Constant
94
+ ):
95
+ val = nxt.value
96
+ if isinstance(getattr(val, "value", None), str) and not str(val.value).strip():
97
+ self._error(
98
+ stmt,
99
+ f"Constant '{target.id}' trailing string literal docstring must not be empty",
100
+ )
101
+
71
102
  # ------------------------------------------------------------------
72
103
  # Function definitions
73
104
  # ------------------------------------------------------------------
@@ -1,53 +0,0 @@
1
- from typing import Annotated
2
-
3
- from axis_synome.spec.asc.entities.primes import EligiblePrimeAgentASC
4
- from axis_synome.spec.asc.entities.types import Position
5
- from axis_synome.spec.asc.formulas.latent_asc import latent_asc
6
- from axis_synome.spec.asc.formulas.resting_asc import resting_asc
7
- from axis_synome.spec_support.metadata import Metadata
8
-
9
-
10
- def collateral_portfolio(
11
- positions: list[Position],
12
- ) -> Annotated[
13
- float,
14
- Metadata(
15
- description="Total Collateral Portfolio value for a prime: sum of all position values.",
16
- source_uuid="64e1390f-68a1-43ec-87a8-8ae7b990f7ec",
17
- ),
18
- ]:
19
- """Total Collateral Portfolio value for a prime: sum of all positions."""
20
- return sum(p.value_usd for p in positions)
21
-
22
-
23
- def asc_collateral_ratio(
24
- prime: EligiblePrimeAgentASC, positions: list[Position]
25
- ) -> Annotated[
26
- float,
27
- Metadata(
28
- description="ASC collateral ratio slack: ASC minus the required minimum fraction of the Collateral Portfolio. Must be >= 0 for compliance.",
29
- source_uuid="475fe222-9e4a-4e9d-9be6-a7a424ce02f8",
30
- ),
31
- ]:
32
- """ASC collateral ratio slack.
33
-
34
- Returns the arithmetic result unconditionally. A negative value means
35
- ASC is below the required minimum fraction of the Collateral Portfolio.
36
- """
37
- agent = prime.value
38
- asc_usd: float = resting_asc(positions) + latent_asc(prime, positions)
39
- portfolio_usd: float = collateral_portfolio(positions)
40
- return asc_usd - portfolio_usd * agent.min_pct_asc_of_collateral
41
-
42
-
43
- def asc_collateral_ratio_satisfied(
44
- prime: EligiblePrimeAgentASC, positions: list[Position]
45
- ) -> Annotated[
46
- bool,
47
- Metadata(
48
- description="True when ASC meets or exceeds the required minimum fraction of the Collateral Portfolio.",
49
- source_uuid="475fe222-9e4a-4e9d-9be6-a7a424ce02f8",
50
- ),
51
- ]:
52
- """Boolean ASC collateral ratio compliance predicate. True iff asc_collateral_ratio >= 0."""
53
- return asc_collateral_ratio(prime, positions) >= 0.0
@@ -1,30 +0,0 @@
1
- from typing import Annotated
2
-
3
- from axis_synome.spec.asc.entities.primes import EligiblePrimeAgentASC
4
- from axis_synome.spec.asc.entities.types import Position
5
- from axis_synome.spec.asc.formulas.latent_asc import latent_asc
6
- from axis_synome.spec.asc.formulas.resting_asc import resting_asc
7
- from axis_synome.spec_support.metadata import Metadata
8
-
9
- DAB_REQUIRED_PCT: Annotated[
10
- float,
11
- Metadata(
12
- description="Required DAB as a fraction of total ASC.",
13
- source_uuid="1e129119-a2ce-4978-b235-c50f2a1c5e2e",
14
- ),
15
- ] = 0.25
16
-
17
-
18
- def dab_required(
19
- prime: EligiblePrimeAgentASC,
20
- positions: list[Position],
21
- ) -> Annotated[
22
- float,
23
- Metadata(
24
- description="Required DAB: DAB_REQUIRED_PCT of total ASC.",
25
- source_uuid="1e129119-a2ce-4978-b235-c50f2a1c5e2e",
26
- ),
27
- ]:
28
- """Required DAB: DAB_REQUIRED_PCT of ASC."""
29
- asc_usd = resting_asc(positions) + latent_asc(prime, positions)
30
- return asc_usd * DAB_REQUIRED_PCT