axis-synome 0.1.0.dev33__tar.gz → 0.1.0.dev35__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 (103) hide show
  1. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/PKG-INFO +1 -1
  2. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/WRITING_SPECS.md +6 -0
  3. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/pyproject.toml +5 -0
  4. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/_version.py +2 -2
  5. axis_synome-0.1.0.dev35/src/axis_synome/spec/asc/entities/alm_proxies.py +209 -0
  6. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec/asc/entities/networks.py +2 -0
  7. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec/asc/entities/primes.py +4 -0
  8. axis_synome-0.1.0.dev35/src/axis_synome/spec_validator/check_source_uuids.py +378 -0
  9. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome.egg-info/PKG-INFO +1 -1
  10. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome.egg-info/SOURCES.txt +4 -0
  11. axis_synome-0.1.0.dev35/tests/axis_synome/asc/test_alm_proxies.py +32 -0
  12. axis_synome-0.1.0.dev35/tests/axis_synome/spec_validator/test_check_source_uuids.py +376 -0
  13. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/.flake8 +0 -0
  14. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/README.md +0 -0
  15. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/setup.cfg +0 -0
  16. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/__init__.py +0 -0
  17. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec/__init__.py +0 -0
  18. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec/asc/README.md +0 -0
  19. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec/asc/entities/__init__.py +0 -0
  20. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec/asc/entities/assets.py +0 -0
  21. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec/asc/entities/assets_by_prime.py +0 -0
  22. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec/asc/entities/protocol_sets.py +0 -0
  23. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec/asc/entities/tokens.py +0 -0
  24. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec/asc/entities/types.py +0 -0
  25. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec/asc/formulas/asc.py +0 -0
  26. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec/asc/formulas/asc_collateral_ratio.py +0 -0
  27. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec/asc/formulas/asc_incentive.py +0 -0
  28. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec/asc/formulas/dab.py +0 -0
  29. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec/asc/formulas/latent_asc.py +0 -0
  30. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec/asc/formulas/ratio_latent_asc.py +0 -0
  31. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec/asc/formulas/resting_asc.py +0 -0
  32. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec/codegen_test/entities/agents.py +0 -0
  33. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec/crypto_lending/__init__.py +0 -0
  34. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec/crypto_lending/formulas/__init__.py +0 -0
  35. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec/crypto_lending/formulas/lif.py +0 -0
  36. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec/risk_capital/__init__.py +0 -0
  37. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec/risk_capital/financial_rrc/__init__.py +0 -0
  38. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec/risk_capital/financial_rrc/entities.py +0 -0
  39. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec/risk_capital/financial_rrc/perpetual_positions.py +0 -0
  40. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec/risk_capital/formulas/__init__.py +0 -0
  41. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec/risk_capital/formulas/required_risk_capital.py +0 -0
  42. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec/suraf/README.md +0 -0
  43. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec/suraf/entities/assessor_score.py +0 -0
  44. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec/suraf/entities/assets.py +0 -0
  45. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec/suraf/entities/mappings.py +0 -0
  46. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec/suraf/formulas/crr.py +0 -0
  47. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec/suraf/formulas/scoring.py +0 -0
  48. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec_support/__init__.py +0 -0
  49. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec_support/evm_address.py +0 -0
  50. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec_support/pendle_validation.py +0 -0
  51. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec_support/runtime/__init__.py +0 -0
  52. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec_support/runtime/base.py +0 -0
  53. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec_support/runtime/math.py +0 -0
  54. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec_support/runtime/reference.py +0 -0
  55. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec_support/validated_dataclass.py +0 -0
  56. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec_support/validated_str.py +0 -0
  57. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec_validator/__init__.py +0 -0
  58. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec_validator/checker.py +0 -0
  59. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec_validator/flake8_plugin.py +0 -0
  60. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome/spec_validator/python_subset.py +0 -0
  61. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome.egg-info/dependency_links.txt +0 -0
  62. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome.egg-info/entry_points.txt +0 -0
  63. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome.egg-info/requires.txt +0 -0
  64. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/src/axis_synome.egg-info/top_level.txt +0 -0
  65. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/tests/axis_synome/asc/__init__.py +0 -0
  66. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/tests/axis_synome/asc/conftest.py +0 -0
  67. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/tests/axis_synome/asc/mocks.py +0 -0
  68. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/tests/axis_synome/asc/test_asc.py +0 -0
  69. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/tests/axis_synome/asc/test_asc_collateral_ratio.py +0 -0
  70. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/tests/axis_synome/asc/test_asc_incentive.py +0 -0
  71. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/tests/axis_synome/asc/test_dab.py +0 -0
  72. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/tests/axis_synome/asc/test_evm_address.py +0 -0
  73. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/tests/axis_synome/asc/test_latent_asc.py +0 -0
  74. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/tests/axis_synome/asc/test_prime_agent_data_validation.py +0 -0
  75. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/tests/axis_synome/asc/test_ratio_latent_asc.py +0 -0
  76. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/tests/axis_synome/asc/test_resting_asc.py +0 -0
  77. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/tests/axis_synome/risk_capital/__init__.py +0 -0
  78. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/tests/axis_synome/risk_capital/financial_rrc/__init__.py +0 -0
  79. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/tests/axis_synome/risk_capital/financial_rrc/test_perpetual_positions.py +0 -0
  80. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/tests/axis_synome/risk_capital/formulas/__init__.py +0 -0
  81. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/tests/axis_synome/risk_capital/formulas/test_loss_given_default.py +0 -0
  82. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/tests/axis_synome/spec_support/__init__.py +0 -0
  83. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/tests/axis_synome/spec_support/runtime/__init__.py +0 -0
  84. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/tests/axis_synome/spec_support/runtime/test_base.py +0 -0
  85. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/tests/axis_synome/spec_support/runtime/test_math.py +0 -0
  86. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/tests/axis_synome/spec_validator/test_checker.py +0 -0
  87. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/tests/axis_synome/spec_validator/test_flake8_plugin.py +0 -0
  88. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/tests/axis_synome/suraf/__init__.py +0 -0
  89. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/tests/axis_synome/suraf/entities/__init__.py +0 -0
  90. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/tests/axis_synome/suraf/entities/test_assessor_score.py +0 -0
  91. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/tests/axis_synome/suraf/formulas/__init__.py +0 -0
  92. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/tests/axis_synome/suraf/formulas/test_crr.py +0 -0
  93. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/tests/axis_synome/suraf/formulas/test_scoring.py +0 -0
  94. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/tests/axis_synome/suraf/static/aave_ausdc/v1/crr_mapping.csv +0 -0
  95. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/tests/axis_synome/suraf/static/aave_ausdc/v1/penalty.csv +0 -0
  96. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/tests/axis_synome/suraf/static/aave_ausdc/v1/scorecards/Assessor_1_scores.csv +0 -0
  97. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/tests/axis_synome/suraf/static/aave_ausdc/v1/scorecards/Assessor_2_scores.csv +0 -0
  98. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/tests/axis_synome/suraf/static/aave_ausdc/v1/scorecards/Assessor_3_scores.csv +0 -0
  99. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/tests/axis_synome/suraf/static/aave_ausdc/v1/weights.csv +0 -0
  100. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/tests/axis_synome/suraf/suraf_client/__init__.py +0 -0
  101. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/tests/axis_synome/suraf/suraf_client/suraf_client.py +0 -0
  102. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/tests/axis_synome/suraf/suraf_client/test_suraf_client.py +0 -0
  103. {axis_synome-0.1.0.dev33 → axis_synome-0.1.0.dev35}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: axis-synome
3
- Version: 0.1.0.dev33
3
+ Version: 0.1.0.dev35
4
4
  Summary: Axis specification modules (entities, formulas, validators)
5
5
  Author-email: Archon Tech <hello@archontech.ai>
6
6
  License: MIT
@@ -14,6 +14,9 @@ This is to facilitate the following goals, paraphrased from the [Axis Synome Tec
14
14
 
15
15
  ## Core Principles
16
16
 
17
+ > If you change this section, also update the distilled review bullets in
18
+ > `.github/instructions/axis-synome.instructions.md`.
19
+
17
20
  Specs encode Atlas calculation rules directly in typed Python.
18
21
  They are declarative, deterministic, side‑effect‑free computations; no I/O or mutable state.
19
22
 
@@ -48,6 +51,9 @@ Relations are traversable: clients and tooling can evaluate predicates across th
48
51
 
49
52
  ### Documentation and Provenance
50
53
 
54
+ > If you change this section, also update the distilled review bullets in
55
+ > `.github/instructions/axis-synome.instructions.md`.
56
+
51
57
  - 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
58
  - As functions are also math formulas and documentation from docstrs will also be used for the mathematical exposition, avoid Python specific terminology.
53
59
  - Avoid duplicating long‑form descriptions in metadata.
@@ -74,3 +74,8 @@ select = [
74
74
  "PGH004",
75
75
  ]
76
76
  ignore = ["E402", "E501"]
77
+
78
+ # Prefer inline suppressions over a [tool.ruff.lint.per-file-ignores] table:
79
+ # put `# ruff: noqa: <CODE>` (file-level) or `# noqa: <CODE>` (line-level) in
80
+ # the source file itself, with a short reason. Keeps the rationale next to the
81
+ # code it justifies and avoids a central table that drifts as files move.
@@ -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.dev33'
22
- __version_tuple__ = version_tuple = (0, 1, 0, 'dev33')
21
+ __version__ = version = '0.1.0.dev35'
22
+ __version_tuple__ = version_tuple = (0, 1, 0, 'dev35')
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -0,0 +1,209 @@
1
+ """ALM Proxy contract deployments per (Prime, Network).
2
+
3
+ The ALM Proxy is the contract authorised to allocate liquidity for a Sky Star
4
+ (Prime) on a given chain. Each deployment is defined as a ``Final`` constant
5
+ carrying its Atlas provenance via a ``:source_uuid:`` docstring marker; the
6
+ ``AlmProxy`` enum at the bottom of the module aggregates the constants into
7
+ a closed-world set for graph traversal.
8
+
9
+ The set below is the union of (a) entries currently approved in the Sky
10
+ Atlas — each constant's ``:source_uuid:`` is the authoritative reference to
11
+ the Atlas document — and (b) entries used by downstream consumers that
12
+ maintain their own hard-coded allocation tracker config. Where the two
13
+ disagree the divergence is documented in the constant's docstring. The
14
+ Atlas is the governance source of truth; entries that exist only on the
15
+ consumer side are kept so that axis_synome remains a superset that those
16
+ consumers can pull from.
17
+ """
18
+
19
+ from enum import Enum
20
+ from typing import Final
21
+
22
+ from axis_synome.spec.asc.entities.networks import Network
23
+ from axis_synome.spec.asc.entities.primes import PrimeAgent
24
+ from axis_synome.spec_support.evm_address import EvmAddress
25
+ from axis_synome.spec_support.validated_dataclass import validated_dataclass
26
+
27
+
28
+ @validated_dataclass
29
+ class AlmProxyDeployment:
30
+ """A Sky Star's ALM Proxy contract deployment on a specific network."""
31
+
32
+ prime: PrimeAgent
33
+ network: Network
34
+ address: EvmAddress
35
+
36
+
37
+ # --- Spark (Atlas ALM Contracts parent :source_uuid: 7db865de-8519-464b-8752-f39ecaf54fd2) ---
38
+
39
+ SPARK_ETHEREUM_MAINNET_ALM_PROXY: Final[AlmProxyDeployment] = AlmProxyDeployment(
40
+ prime=PrimeAgent.SPARK,
41
+ network=Network.ETHEREUM_MAINNET,
42
+ address=EvmAddress("0x1601843c5E9bC251A3272907010AFa41Fa18347E"),
43
+ )
44
+ """ALM Proxy contract deployment for Spark on Ethereum Mainnet.
45
+
46
+ :source_uuid: a29a6751-4809-446c-a659-0dd93ca40379
47
+ """
48
+
49
+
50
+ SPARK_BASE_ALM_PROXY: Final[AlmProxyDeployment] = AlmProxyDeployment(
51
+ prime=PrimeAgent.SPARK,
52
+ network=Network.BASE,
53
+ address=EvmAddress("0x2917956eFF0B5eaF030abDB4EF4296DF775009cA"),
54
+ )
55
+ """ALM Proxy contract deployment for Spark on Base.
56
+
57
+ :source_uuid: 425339ce-8e44-430b-ab8c-6c69f0b757e9
58
+ """
59
+
60
+
61
+ SPARK_ARBITRUM_ALM_PROXY: Final[AlmProxyDeployment] = AlmProxyDeployment(
62
+ prime=PrimeAgent.SPARK,
63
+ network=Network.ARBITRUM,
64
+ address=EvmAddress("0x92afd6F2385a90e44da3a8B60fe36f6cBe1D8709"),
65
+ )
66
+ """ALM Proxy contract deployment for Spark on Arbitrum.
67
+
68
+ :source_uuid: c671b407-fcb2-48eb-8217-2ec156b581ad
69
+ """
70
+
71
+
72
+ SPARK_UNICHAIN_ALM_PROXY: Final[AlmProxyDeployment] = AlmProxyDeployment(
73
+ prime=PrimeAgent.SPARK,
74
+ network=Network.UNICHAIN,
75
+ address=EvmAddress("0x345E368fcCd62266B3f5F37C9a131FD1c39f5869"),
76
+ )
77
+ """ALM Proxy contract deployment for Spark on Unichain.
78
+
79
+ :source_uuid: 6affe08d-0c1c-4cbf-a100-4a04c58220bb
80
+ """
81
+
82
+
83
+ SPARK_OPTIMISM_ALM_PROXY: Final[AlmProxyDeployment] = AlmProxyDeployment(
84
+ prime=PrimeAgent.SPARK,
85
+ network=Network.OPTIMISM,
86
+ address=EvmAddress("0x876664f0c9Ff24D1aa355Ce9f1680AE1A5bf36fB"),
87
+ )
88
+ """ALM Proxy contract deployment for Spark on Optimism.
89
+
90
+ :source_uuid: f1895dfc-a18c-4009-bfd3-1c16c9a62092
91
+ """
92
+
93
+
94
+ SPARK_AVALANCHE_ALM_PROXY: Final[AlmProxyDeployment] = AlmProxyDeployment(
95
+ prime=PrimeAgent.SPARK,
96
+ network=Network.AVALANCHE,
97
+ address=EvmAddress("0xecE6B0E8a54c2f44e066fBb9234e7157B15b7FeC"),
98
+ )
99
+ """ALM Proxy contract deployment for Spark on Avalanche.
100
+
101
+ Discrepancy: the Atlas document below records the address as ``TBD`` while
102
+ sentinel stl-verify config.go already uses the address encoded above. The
103
+ ``:source_uuid:`` still points at the Atlas document so the entry remains
104
+ auditable once the Atlas value is filled in.
105
+
106
+ :source_uuid: 179f186a-079b-4663-b06c-b21f9dec85ca
107
+ """
108
+
109
+
110
+ # --- Grove (Atlas ALM Contracts parent :source_uuid: f233a46b-8dff-4335-8ccf-dc3f1c18a96f) ---
111
+
112
+ GROVE_ETHEREUM_MAINNET_ALM_PROXY: Final[AlmProxyDeployment] = AlmProxyDeployment(
113
+ prime=PrimeAgent.GROVE,
114
+ network=Network.ETHEREUM_MAINNET,
115
+ address=EvmAddress("0x491EDFB0B8b608044e227225C715981a30F3A44E"),
116
+ )
117
+ """ALM Proxy contract deployment for Grove on Ethereum Mainnet.
118
+
119
+ :source_uuid: fda13ac2-b3ed-4b2a-9be6-9247632dafe3
120
+ """
121
+
122
+
123
+ GROVE_AVALANCHE_ALM_PROXY: Final[AlmProxyDeployment] = AlmProxyDeployment(
124
+ prime=PrimeAgent.GROVE,
125
+ network=Network.AVALANCHE,
126
+ address=EvmAddress("0x7107DD8F56642327945294a18A4280C78e153644"),
127
+ )
128
+ """ALM Proxy contract deployment for Grove on Avalanche.
129
+
130
+ :source_uuid: 0704f4b5-ee5c-455c-932f-94591b8a6594
131
+ """
132
+
133
+
134
+ GROVE_BASE_ALM_PROXY: Final[AlmProxyDeployment] = AlmProxyDeployment(
135
+ prime=PrimeAgent.GROVE,
136
+ network=Network.BASE,
137
+ address=EvmAddress("0x9B746dBC5269e1DF6e4193Bcb441C0FbBF1CeCEe"),
138
+ )
139
+ """ALM Proxy contract deployment for Grove on Base.
140
+
141
+ Discrepancy: sentinel stl-verify config.go does not currently include this
142
+ deployment; the address below is sourced from the Atlas document referenced
143
+ by ``:source_uuid:`` and should be added to the Go side.
144
+
145
+ :source_uuid: 5c382a94-ce36-4ffa-862b-4718382450fe
146
+ """
147
+
148
+
149
+ GROVE_PLASMA_ALM_PROXY: Final[AlmProxyDeployment] = AlmProxyDeployment(
150
+ prime=PrimeAgent.GROVE,
151
+ network=Network.PLASMA,
152
+ address=EvmAddress("0x0C462Fff7Cc975bC9F2B0aEB8270febA5FD71e1B"),
153
+ )
154
+ """ALM Proxy contract deployment for Grove on Plasma.
155
+
156
+ Discrepancy: sentinel stl-verify config.go does not currently include this
157
+ deployment; the address below is sourced from the Atlas document referenced
158
+ by ``:source_uuid:`` and should be added to the Go side.
159
+
160
+ :source_uuid: 9d0bcc23-02d4-4389-9c85-707acf900dee
161
+ """
162
+
163
+
164
+ GROVE_PLUME_ALM_PROXY: Final[AlmProxyDeployment] = AlmProxyDeployment(
165
+ prime=PrimeAgent.GROVE,
166
+ network=Network.PLUME,
167
+ address=EvmAddress("0x1DB91ad50446a671e2231f77e00948E68876F812"),
168
+ )
169
+ """ALM Proxy contract deployment for Grove on Plume.
170
+
171
+ :source_uuid: dcf0beac-b93e-41a7-b8b6-98c1d4cc819b
172
+ """
173
+
174
+
175
+ GROVE_MONAD_ALM_PROXY: Final[AlmProxyDeployment] = AlmProxyDeployment(
176
+ prime=PrimeAgent.GROVE,
177
+ network=Network.MONAD,
178
+ address=EvmAddress("0x94B398ACb2fcE988871218221EA6a4a2b26CcCbC"),
179
+ )
180
+ """ALM Proxy contract deployment for Grove on Monad.
181
+
182
+ Discrepancy: no Atlas ALM Proxy Contract document exists for Grove on Monad
183
+ yet, so ``:source_uuid:`` is omitted. The address is sourced from sentinel
184
+ stl-verify config.go and should be backfilled in the Atlas.
185
+ """
186
+
187
+
188
+ class AlmProxy(Enum):
189
+ """Closed-world set of all known ALM Proxy deployments.
190
+
191
+ Each member's value is one of the ``Final[AlmProxyDeployment]`` constants
192
+ defined above. The constants carry the canonical ``:source_uuid:`` markers
193
+ used by the synome formula extractor; this enum exists so that downstream
194
+ consumers can traverse the deployments as a closed set.
195
+ """
196
+
197
+ SPARK_ETHEREUM_MAINNET = SPARK_ETHEREUM_MAINNET_ALM_PROXY
198
+ SPARK_BASE = SPARK_BASE_ALM_PROXY
199
+ SPARK_ARBITRUM = SPARK_ARBITRUM_ALM_PROXY
200
+ SPARK_UNICHAIN = SPARK_UNICHAIN_ALM_PROXY
201
+ SPARK_OPTIMISM = SPARK_OPTIMISM_ALM_PROXY
202
+ SPARK_AVALANCHE = SPARK_AVALANCHE_ALM_PROXY
203
+
204
+ GROVE_ETHEREUM_MAINNET = GROVE_ETHEREUM_MAINNET_ALM_PROXY
205
+ GROVE_AVALANCHE = GROVE_AVALANCHE_ALM_PROXY
206
+ GROVE_BASE = GROVE_BASE_ALM_PROXY
207
+ GROVE_PLASMA = GROVE_PLASMA_ALM_PROXY
208
+ GROVE_PLUME = GROVE_PLUME_ALM_PROXY
209
+ GROVE_MONAD = GROVE_MONAD_ALM_PROXY
@@ -9,6 +9,8 @@ class Network(Enum):
9
9
  UNICHAIN = "Unichain"
10
10
  AVALANCHE = "Avalanche"
11
11
  PLASMA = "Plasma"
12
+ PLUME = "Plume"
13
+ MONAD = "Monad"
12
14
 
13
15
 
14
16
  # L2 networks: all networks that are not Ethereum mainnet.
@@ -39,6 +39,8 @@ class PrimeAgent(Enum):
39
39
  Network.AVALANCHE,
40
40
  Network.BASE,
41
41
  Network.ETHEREUM_MAINNET,
42
+ Network.OPTIMISM,
43
+ Network.UNICHAIN,
42
44
  },
43
45
  )
44
46
  GROVE = PrimeAgentData(
@@ -51,6 +53,8 @@ class PrimeAgent(Enum):
51
53
  Network.BASE,
52
54
  Network.ETHEREUM_MAINNET,
53
55
  Network.PLASMA,
56
+ Network.PLUME,
57
+ Network.MONAD,
54
58
  },
55
59
  )
56
60
  KEEL = PrimeAgentData(
@@ -0,0 +1,378 @@
1
+ """Verify each ``:source_uuid:`` in spec docstrings resolves to an ``id`` in the
2
+ Atlas ``content/`` tree (one ``document.md`` per Atlas section, with YAML
3
+ frontmatter carrying ``id: <uuid>``).
4
+ """
5
+
6
+ # ruff: noqa: T201 -- CLI entry point legitimately prints to stdout/stderr.
7
+
8
+ from __future__ import annotations
9
+
10
+ import argparse
11
+ import ast
12
+ import json
13
+ import re
14
+ import sys
15
+ from collections import defaultdict
16
+ from collections.abc import Iterable, Iterator
17
+ from dataclasses import dataclass
18
+ from pathlib import Path
19
+
20
+ # Canonical 8-4-4-4-12 hex UUID. Case-insensitive; we lowercase before compare.
21
+ _UUID_RE = re.compile(
22
+ r"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"
23
+ )
24
+
25
+ # Generic reST field at the start of a docstring line: ``:name: value``.
26
+ # ``name`` is restricted to identifier-shaped tokens so we don't match URLs etc.
27
+ _FIELD_LINE_RE = re.compile(r"^:([A-Za-z][A-Za-z0-9_-]*):\s*(.*)$")
28
+
29
+ # Allowlist of recognised docstring field names. Anything else triggers a
30
+ # warning so typos and unintended drift surface early.
31
+ ALLOWED_DOCSTRING_FIELDS: frozenset[str] = frozenset({"source_uuid"})
32
+
33
+ # YAML frontmatter ``id:`` field. We don't pull in a YAML parser for one line;
34
+ # the field is always emitted as ``id: <uuid>`` by the Atlas decompose tool.
35
+ _FRONTMATTER_ID_RE = re.compile(r"^id:\s+(" + _UUID_RE.pattern + r")\s*$")
36
+
37
+ # Defaults derived from this file's location so the CLI works from any cwd.
38
+ # `parents` indices: [0]=spec_validator, [1]=axis_synome (under src),
39
+ # [2]=src, [3]=axis_synome (package dir), [4]=python, [5]=repo root.
40
+ _HERE = Path(__file__).resolve()
41
+ DEFAULT_SPEC_ROOT: Path = _HERE.parents[1] / "spec"
42
+ DEFAULT_CONTENT_ROOT: Path = _HERE.parents[5] / "content"
43
+
44
+
45
+ @dataclass(frozen=True)
46
+ class SpecRef:
47
+ """A single ``:source_uuid:`` reference extracted from a spec file."""
48
+
49
+ path: Path
50
+ lineno: int
51
+ symbol: str
52
+ uuid: str # lowercased
53
+
54
+
55
+ @dataclass(frozen=True)
56
+ class FieldWarning:
57
+ """A non-fatal issue extracted alongside the spec references."""
58
+
59
+ path: Path
60
+ lineno: int
61
+ message: str
62
+
63
+
64
+ @dataclass(frozen=True)
65
+ class CheckResult:
66
+ spec_refs: list[SpecRef]
67
+ atlas_uuids: frozenset[str] # lowercased
68
+ missing: dict[str, list[SpecRef]] # uuid -> refs that point at it
69
+ warnings: list[FieldWarning]
70
+
71
+
72
+ # --------------------------------------------------------------------------- #
73
+ # Spec-side extraction
74
+ # --------------------------------------------------------------------------- #
75
+
76
+
77
+ def _iter_spec_files(spec_root: Path) -> Iterator[Path]:
78
+ yield from sorted(p for p in spec_root.rglob("*.py") if "__pycache__" not in p.parts)
79
+
80
+
81
+ def _process_docstring(
82
+ docstring: str,
83
+ *,
84
+ path: Path,
85
+ base_lineno: int,
86
+ symbol: str,
87
+ ) -> tuple[list[SpecRef], list[FieldWarning]]:
88
+ """Extract every reST ``:name: value`` field from a docstring.
89
+
90
+ For ``:source_uuid:`` we yield a :class:`SpecRef` (with a malformed-UUID
91
+ warning if the value isn't canonical 8-4-4-4-12 hex). For any other field
92
+ name we yield an "unknown field" warning so the allowlist in
93
+ :data:`ALLOWED_DOCSTRING_FIELDS` stays the single point of truth.
94
+ """
95
+ refs: list[SpecRef] = []
96
+ warnings: list[FieldWarning] = []
97
+ for offset, raw in enumerate(docstring.splitlines()):
98
+ line = raw.strip()
99
+ match = _FIELD_LINE_RE.match(line)
100
+ if not match:
101
+ continue
102
+ name = match.group(1)
103
+ value = match.group(2).strip()
104
+ lineno = base_lineno + offset
105
+
106
+ if name == "source_uuid":
107
+ uuid_match = _UUID_RE.fullmatch(value)
108
+ if uuid_match is None:
109
+ warnings.append(
110
+ FieldWarning(
111
+ path=path,
112
+ lineno=lineno,
113
+ message=(
114
+ f"':source_uuid:' value is not a canonical 8-4-4-4-12 "
115
+ f"hex UUID: {value!r}"
116
+ ),
117
+ )
118
+ )
119
+ continue
120
+ refs.append(
121
+ SpecRef(
122
+ path=path,
123
+ lineno=lineno,
124
+ symbol=symbol,
125
+ uuid=value.lower(),
126
+ )
127
+ )
128
+ continue
129
+
130
+ if name not in ALLOWED_DOCSTRING_FIELDS:
131
+ warnings.append(
132
+ FieldWarning(
133
+ path=path,
134
+ lineno=lineno,
135
+ message=(
136
+ f"unknown docstring field ':{name}:' — "
137
+ f"allowed fields: {sorted(ALLOWED_DOCSTRING_FIELDS)}"
138
+ ),
139
+ )
140
+ )
141
+
142
+ return refs, warnings
143
+
144
+
145
+ def _docstring_node(body: list[ast.stmt]) -> tuple[str, int] | None:
146
+ """Return ``(docstring, lineno)`` if first stmt is a bare string-literal Expr."""
147
+ if not body:
148
+ return None
149
+ first = body[0]
150
+ if (
151
+ isinstance(first, ast.Expr)
152
+ and isinstance(first.value, ast.Constant)
153
+ and isinstance(first.value.value, str)
154
+ ):
155
+ return first.value.value, first.value.lineno
156
+ return None
157
+
158
+
159
+ def _trailing_const_docstrings(body: list[ast.stmt]) -> Iterator[tuple[str, str, int]]:
160
+ """Yield ``(target_name, docstring, lineno)`` for ``X: Final[...] = v`` followed by ``\"\"\"...\"\"\"``.
161
+
162
+ Mirrors :class:`SpecChecker._validate_constant_docstrings` but only needs
163
+ the ``(name, docstring)`` pairs — we don't enforce structure here.
164
+ """
165
+ for prev, curr in zip(body, body[1:], strict=False):
166
+ if not (
167
+ isinstance(curr, ast.Expr)
168
+ and isinstance(curr.value, ast.Constant)
169
+ and isinstance(curr.value.value, str)
170
+ ):
171
+ continue
172
+ target_name: str | None = None
173
+ if isinstance(prev, ast.AnnAssign) and isinstance(prev.target, ast.Name):
174
+ target_name = prev.target.id
175
+ elif (
176
+ isinstance(prev, ast.Assign)
177
+ and len(prev.targets) == 1
178
+ and isinstance(prev.targets[0], ast.Name)
179
+ ):
180
+ target_name = prev.targets[0].id
181
+ if target_name is not None:
182
+ yield target_name, curr.value.value, curr.value.lineno
183
+
184
+
185
+ def extract_spec_refs(path: Path) -> tuple[list[SpecRef], list[FieldWarning]]:
186
+ """Extract every ``:source_uuid:`` reference (and field warnings) from one spec file."""
187
+ source = path.read_text(encoding="utf-8")
188
+ try:
189
+ tree = ast.parse(source, filename=str(path))
190
+ except SyntaxError as exc:
191
+ return [], [
192
+ FieldWarning(path=path, lineno=exc.lineno or 0, message=f"syntax error: {exc.msg}")
193
+ ]
194
+
195
+ refs: list[SpecRef] = []
196
+ warnings: list[FieldWarning] = []
197
+
198
+ module_doc = _docstring_node(tree.body)
199
+ if module_doc is not None:
200
+ text, lineno = module_doc
201
+ r, w = _process_docstring(text, path=path, base_lineno=lineno, symbol="<module>")
202
+ refs.extend(r)
203
+ warnings.extend(w)
204
+
205
+ for node in ast.walk(tree):
206
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
207
+ doc = _docstring_node(node.body)
208
+ if doc is not None:
209
+ text, lineno = doc
210
+ r, w = _process_docstring(text, path=path, base_lineno=lineno, symbol=node.name)
211
+ refs.extend(r)
212
+ warnings.extend(w)
213
+ if isinstance(node, (ast.Module, ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
214
+ for name, text, lineno in _trailing_const_docstrings(list(node.body)):
215
+ r, w = _process_docstring(text, path=path, base_lineno=lineno, symbol=name)
216
+ refs.extend(r)
217
+ warnings.extend(w)
218
+
219
+ return refs, warnings
220
+
221
+
222
+ # --------------------------------------------------------------------------- #
223
+ # Atlas-side extraction
224
+ # --------------------------------------------------------------------------- #
225
+
226
+
227
+ def extract_atlas_uuids(content_root: Path) -> frozenset[str]:
228
+ """Extract ``id`` UUIDs from YAML frontmatter of every ``document.md`` under
229
+ ``content_root``. ``_index.md`` files (navigation only) are skipped.
230
+ """
231
+ uuids: set[str] = set()
232
+ for path in sorted(content_root.rglob("document.md")):
233
+ with path.open(encoding="utf-8") as fh:
234
+ if fh.readline().rstrip("\r\n") != "---":
235
+ continue
236
+ for raw in fh:
237
+ line = raw.rstrip("\r\n")
238
+ if line == "---":
239
+ break
240
+ match = _FRONTMATTER_ID_RE.match(line)
241
+ if match:
242
+ uuids.add(match.group(1).lower())
243
+ break
244
+ return frozenset(uuids)
245
+
246
+
247
+ # --------------------------------------------------------------------------- #
248
+ # Orchestration
249
+ # --------------------------------------------------------------------------- #
250
+
251
+
252
+ def check(
253
+ spec_root: Path,
254
+ content_root: Path | None,
255
+ ) -> CheckResult:
256
+ """Run extraction on both sides and compute the missing / warning sets.
257
+
258
+ If ``content_root`` is ``None`` the Atlas side is treated as empty and no
259
+ "missing" entries are produced — callers decide whether that's an error.
260
+ """
261
+ spec_refs: list[SpecRef] = []
262
+ warnings: list[FieldWarning] = []
263
+ for path in _iter_spec_files(spec_root):
264
+ r, w = extract_spec_refs(path)
265
+ spec_refs.extend(r)
266
+ warnings.extend(w)
267
+
268
+ by_uuid: dict[str, list[SpecRef]] = defaultdict(list)
269
+ for ref in spec_refs:
270
+ by_uuid[ref.uuid].append(ref)
271
+
272
+ # Duplicate-UUID across distinct *files* → warning. Multiple symbols in
273
+ # the same module pointing at the same Atlas section is the natural pattern
274
+ # (e.g. an arithmetic formula and its boolean predicate variant); only
275
+ # cross-file reuse is suspicious enough to surface.
276
+ for uuid, refs in by_uuid.items():
277
+ files = {r.path for r in refs}
278
+ if len(files) > 1:
279
+ sample = refs[0]
280
+ others = [r for r in refs if r.path != sample.path]
281
+ other_locations = ", ".join(f"{r.path}:{r.lineno}" for r in others)
282
+ warnings.append(
283
+ FieldWarning(
284
+ path=sample.path,
285
+ lineno=sample.lineno,
286
+ message=(
287
+ f"UUID {uuid} reused across distinct files (also at {other_locations})"
288
+ ),
289
+ )
290
+ )
291
+
292
+ if content_root is None:
293
+ atlas_uuids: frozenset[str] = frozenset()
294
+ missing: dict[str, list[SpecRef]] = {}
295
+ else:
296
+ atlas_uuids = extract_atlas_uuids(content_root)
297
+ missing = {uuid: refs for uuid, refs in by_uuid.items() if uuid not in atlas_uuids}
298
+
299
+ return CheckResult(
300
+ spec_refs=spec_refs,
301
+ atlas_uuids=atlas_uuids,
302
+ missing=missing,
303
+ warnings=warnings,
304
+ )
305
+
306
+
307
+ # --------------------------------------------------------------------------- #
308
+ # CLI
309
+ # --------------------------------------------------------------------------- #
310
+
311
+
312
+ def _format_missing(missing: dict[str, list[SpecRef]]) -> str:
313
+ lines = ["ERROR: spec :source_uuid: values not found in the Atlas content tree:"]
314
+ for uuid in sorted(missing):
315
+ refs = missing[uuid]
316
+ lines.append(f" {uuid}")
317
+ for ref in refs:
318
+ lines.append(f" {ref.path}:{ref.lineno} ({ref.symbol})")
319
+ return "\n".join(lines)
320
+
321
+
322
+ def _emit_map(result: CheckResult, path: Path) -> None:
323
+ payload = {
324
+ "spec_to_atlas": [
325
+ {
326
+ "uuid": ref.uuid,
327
+ "spec_path": str(ref.path),
328
+ "lineno": ref.lineno,
329
+ "symbol": ref.symbol,
330
+ }
331
+ for ref in sorted(result.spec_refs, key=lambda r: (str(r.path), r.lineno))
332
+ ],
333
+ }
334
+ path.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8")
335
+
336
+
337
+ def main(argv: Iterable[str] | None = None) -> int:
338
+ parser = argparse.ArgumentParser(
339
+ prog="check-source-uuids",
340
+ description="Verify spec :source_uuid: references resolve to UUIDs in the Atlas content tree.",
341
+ )
342
+ parser.add_argument(
343
+ "--emit-map",
344
+ type=Path,
345
+ default=None,
346
+ help="Optional path to write a JSON spec↔atlas map.",
347
+ )
348
+ args = parser.parse_args(list(argv) if argv is not None else None)
349
+
350
+ if not DEFAULT_SPEC_ROOT.is_dir():
351
+ print(f"ERROR: spec root not found: {DEFAULT_SPEC_ROOT}", file=sys.stderr)
352
+ return 2
353
+ if not DEFAULT_CONTENT_ROOT.is_dir():
354
+ print(f"ERROR: Atlas content tree not found: {DEFAULT_CONTENT_ROOT}", file=sys.stderr)
355
+ return 2
356
+
357
+ result = check(DEFAULT_SPEC_ROOT, DEFAULT_CONTENT_ROOT)
358
+
359
+ for warn in result.warnings:
360
+ print(f"WARN: {warn.path}:{warn.lineno}: {warn.message}", file=sys.stderr)
361
+
362
+ if args.emit_map is not None:
363
+ _emit_map(result, args.emit_map)
364
+
365
+ if result.missing:
366
+ print(_format_missing(result.missing), file=sys.stderr)
367
+ return 1
368
+
369
+ print(
370
+ f"OK: all {len(result.spec_refs)} spec :source_uuid: values "
371
+ f"({len({r.uuid for r in result.spec_refs})} unique) "
372
+ f"resolve to documents under {DEFAULT_CONTENT_ROOT}."
373
+ )
374
+ return 0
375
+
376
+
377
+ if __name__ == "__main__":
378
+ raise SystemExit(main())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: axis-synome
3
- Version: 0.1.0.dev33
3
+ Version: 0.1.0.dev35
4
4
  Summary: Axis specification modules (entities, formulas, validators)
5
5
  Author-email: Archon Tech <hello@archontech.ai>
6
6
  License: MIT