mxm-runtime 0.1.0__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.
- mxm_runtime-0.1.0/LICENSE +21 -0
- mxm_runtime-0.1.0/PKG-INFO +378 -0
- mxm_runtime-0.1.0/README.md +351 -0
- mxm_runtime-0.1.0/pyproject.toml +76 -0
- mxm_runtime-0.1.0/src/mxm/runtime/__init__.py +12 -0
- mxm_runtime-0.1.0/src/mxm/runtime/attestation.py +57 -0
- mxm_runtime-0.1.0/src/mxm/runtime/build.py +79 -0
- mxm_runtime-0.1.0/src/mxm/runtime/cli.py +0 -0
- mxm_runtime-0.1.0/src/mxm/runtime/context.py +37 -0
- mxm_runtime-0.1.0/src/mxm/runtime/discovery.py +68 -0
- mxm_runtime-0.1.0/src/mxm/runtime/identity.py +59 -0
- mxm_runtime-0.1.0/src/mxm/runtime/py.typed +0 -0
- mxm_runtime-0.1.0/src/mxm/runtime/validation.py +32 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 Money Ex Machina
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the “Software”), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mxm-runtime
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Runtime identity discovery and runtime context construction for Money Ex Machina.
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Keywords: runtime,context,identity,configuration,deployment,infrastructure,orchestration,prefect,mxm
|
|
8
|
+
Author: mxm
|
|
9
|
+
Author-email: contact@moneyexmachina.com
|
|
10
|
+
Requires-Python: >=3.13,<3.15
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Dist: mxm-config (>=0.6.2,<0.7)
|
|
19
|
+
Requires-Dist: mxm-secrets (>=0.3.2,<0.4)
|
|
20
|
+
Requires-Dist: mxm-types (>=0.3.1,<0.4.0)
|
|
21
|
+
Requires-Dist: rich (>=14.2.0,<15.0.0)
|
|
22
|
+
Requires-Dist: typer (>=0.20.0,<0.21.0)
|
|
23
|
+
Project-URL: Homepage, https://github.com/moneyexmachina/mxm-runtime
|
|
24
|
+
Project-URL: Repository, https://github.com/moneyexmachina/mxm-runtime
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# mxm-runtime
|
|
28
|
+
|
|
29
|
+

|
|
30
|
+

|
|
31
|
+

|
|
32
|
+
[](https://microsoft.github.io/pyright/)
|
|
33
|
+
|
|
34
|
+
Runtime discovery, configuration-driven service construction, and RuntimeContext assembly for the Money Ex Machina ecosystem.
|
|
35
|
+
|
|
36
|
+
`mxm-runtime` is responsible for constructing the operational environment in which MXM applications execute.
|
|
37
|
+
|
|
38
|
+
It discovers runtime characteristics, loads and resolves configuration, constructs configured services, and assembles them into a single RuntimeContext.
|
|
39
|
+
|
|
40
|
+
## Purpose
|
|
41
|
+
|
|
42
|
+
MXM applications require more than configuration files.
|
|
43
|
+
|
|
44
|
+
They require an operational environment:
|
|
45
|
+
|
|
46
|
+
```text
|
|
47
|
+
Who am I?
|
|
48
|
+
Where am I running?
|
|
49
|
+
Which configuration applies?
|
|
50
|
+
Which services are available?
|
|
51
|
+
Where should data and artefacts live?
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`mxm-runtime` answers these questions.
|
|
55
|
+
|
|
56
|
+
It exists to separate:
|
|
57
|
+
|
|
58
|
+
```text
|
|
59
|
+
application code
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
from:
|
|
63
|
+
|
|
64
|
+
```text
|
|
65
|
+
runtime discovery
|
|
66
|
+
configuration loading
|
|
67
|
+
service construction
|
|
68
|
+
deployment concerns
|
|
69
|
+
context assembly
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Applications should not:
|
|
73
|
+
|
|
74
|
+
- discover machine characteristics,
|
|
75
|
+
- determine deployment substrate,
|
|
76
|
+
- load configuration directly,
|
|
77
|
+
- construct service APIs,
|
|
78
|
+
- or reason about deployment topology.
|
|
79
|
+
|
|
80
|
+
Instead, applications receive a configured RuntimeContext:
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
context = build_runtime_context(
|
|
84
|
+
identity=runtime_identity,
|
|
85
|
+
)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
and consume the services and resources provided by that context.
|
|
89
|
+
|
|
90
|
+
## Architecture
|
|
91
|
+
|
|
92
|
+
`mxm-runtime` acts as the runtime constructor layer of the MXM architecture.
|
|
93
|
+
|
|
94
|
+
```text
|
|
95
|
+
RuntimeIdentity
|
|
96
|
+
↓
|
|
97
|
+
mxm-config
|
|
98
|
+
↓
|
|
99
|
+
Configuration Resolution
|
|
100
|
+
↓
|
|
101
|
+
Service Construction
|
|
102
|
+
↓
|
|
103
|
+
RuntimeContext
|
|
104
|
+
↓
|
|
105
|
+
Application
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The package owns runtime construction.
|
|
109
|
+
|
|
110
|
+
It does not own configuration semantics or secret resolution semantics.
|
|
111
|
+
|
|
112
|
+
Those responsibilities belong to:
|
|
113
|
+
|
|
114
|
+
```text
|
|
115
|
+
mxm-config
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
and:
|
|
119
|
+
|
|
120
|
+
```text
|
|
121
|
+
mxm-secrets
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
respectively.
|
|
125
|
+
|
|
126
|
+
## Core Concepts
|
|
127
|
+
|
|
128
|
+
### RuntimeIdentity
|
|
129
|
+
|
|
130
|
+
Represents the operational identity of a running process.
|
|
131
|
+
|
|
132
|
+
Example:
|
|
133
|
+
|
|
134
|
+
```text
|
|
135
|
+
app mxm-moneymachine
|
|
136
|
+
environment dev
|
|
137
|
+
machine bridge
|
|
138
|
+
substrate local-process
|
|
139
|
+
role marketdata
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Runtime identity determines which configuration layers are selected and which services are constructed.
|
|
143
|
+
|
|
144
|
+
### Machine
|
|
145
|
+
|
|
146
|
+
A machine identifies a machine-specific configuration profile.
|
|
147
|
+
|
|
148
|
+
Examples:
|
|
149
|
+
|
|
150
|
+
```text
|
|
151
|
+
bridge
|
|
152
|
+
monolith
|
|
153
|
+
wildling
|
|
154
|
+
scribe
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Machine values are derived from operating-system characteristics and are used to select machine-specific configuration.
|
|
158
|
+
|
|
159
|
+
They are configuration selectors rather than unique hardware identifiers.
|
|
160
|
+
|
|
161
|
+
### Substrate
|
|
162
|
+
|
|
163
|
+
Represents the execution substrate.
|
|
164
|
+
|
|
165
|
+
Examples:
|
|
166
|
+
|
|
167
|
+
```text
|
|
168
|
+
local-process
|
|
169
|
+
docker
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Substrate allows runtime construction to adapt to deployment environment differences.
|
|
173
|
+
|
|
174
|
+
### RuntimeContext
|
|
175
|
+
|
|
176
|
+
Represents the fully constructed operational environment.
|
|
177
|
+
|
|
178
|
+
Current fields:
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
RuntimeContext(
|
|
182
|
+
identity=...,
|
|
183
|
+
config=...,
|
|
184
|
+
secrets=...,
|
|
185
|
+
)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Future versions may additionally materialise:
|
|
189
|
+
|
|
190
|
+
```text
|
|
191
|
+
paths
|
|
192
|
+
databases
|
|
193
|
+
reference data services
|
|
194
|
+
storage services
|
|
195
|
+
execution services
|
|
196
|
+
reporting services
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Applications are expected to consume runtime services through RuntimeContext.
|
|
200
|
+
|
|
201
|
+
## Runtime Construction Flow
|
|
202
|
+
|
|
203
|
+
Runtime construction follows the sequence:
|
|
204
|
+
|
|
205
|
+
```text
|
|
206
|
+
RuntimeIdentity
|
|
207
|
+
↓
|
|
208
|
+
load_config(...)
|
|
209
|
+
↓
|
|
210
|
+
Configuration Views
|
|
211
|
+
↓
|
|
212
|
+
Service Construction
|
|
213
|
+
↓
|
|
214
|
+
RuntimeContext
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
For example:
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
context = build_runtime_context(
|
|
221
|
+
identity=identity,
|
|
222
|
+
)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
which currently materialises:
|
|
226
|
+
|
|
227
|
+
```text
|
|
228
|
+
configuration
|
|
229
|
+
secret services
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
and returns a configured RuntimeContext.
|
|
233
|
+
|
|
234
|
+
## Runtime Discovery
|
|
235
|
+
|
|
236
|
+
`mxm-runtime` provides discovery utilities for determining runtime characteristics.
|
|
237
|
+
|
|
238
|
+
Examples:
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
machine = discover_machine()
|
|
242
|
+
substrate = discover_substrate()
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
These functions derive MXM runtime selectors from operating-system facts.
|
|
246
|
+
|
|
247
|
+
The resulting values are suitable for configuration resolution and runtime construction.
|
|
248
|
+
|
|
249
|
+
## Relationship To mxm-config
|
|
250
|
+
|
|
251
|
+
`mxm-config` owns:
|
|
252
|
+
|
|
253
|
+
```text
|
|
254
|
+
configuration storage
|
|
255
|
+
configuration loading
|
|
256
|
+
configuration merging
|
|
257
|
+
configuration views
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
`mxm-runtime` consumes configuration and constructs runtime services from it.
|
|
261
|
+
|
|
262
|
+
Example:
|
|
263
|
+
|
|
264
|
+
```text
|
|
265
|
+
RuntimeIdentity
|
|
266
|
+
↓
|
|
267
|
+
mxm-config
|
|
268
|
+
↓
|
|
269
|
+
MXMConfig
|
|
270
|
+
↓
|
|
271
|
+
RuntimeContext
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## Relationship To mxm-secrets
|
|
275
|
+
|
|
276
|
+
`mxm-secrets` owns:
|
|
277
|
+
|
|
278
|
+
```text
|
|
279
|
+
secret references
|
|
280
|
+
authorization
|
|
281
|
+
resolution
|
|
282
|
+
retrieval
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
`mxm-runtime` constructs configured secret services and makes them available through RuntimeContext.
|
|
286
|
+
|
|
287
|
+
Applications are expected to consume:
|
|
288
|
+
|
|
289
|
+
```python
|
|
290
|
+
context.secrets
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
rather than constructing SecretsApi instances directly.
|
|
294
|
+
|
|
295
|
+
## Installation
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
pip install mxm-runtime
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## Usage
|
|
302
|
+
|
|
303
|
+
Construct a RuntimeContext:
|
|
304
|
+
|
|
305
|
+
```python
|
|
306
|
+
from mxm.runtime import build_runtime_context
|
|
307
|
+
|
|
308
|
+
context = build_runtime_context(
|
|
309
|
+
identity=identity,
|
|
310
|
+
)
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Access configured services:
|
|
314
|
+
|
|
315
|
+
```python
|
|
316
|
+
api_key = context.secrets.get_secret(
|
|
317
|
+
"databento_api_key",
|
|
318
|
+
identity=context.identity,
|
|
319
|
+
)
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## Design Principles
|
|
323
|
+
|
|
324
|
+
- **Explicit runtime identity**
|
|
325
|
+
Runtime identity is always represented explicitly.
|
|
326
|
+
|
|
327
|
+
- **Configuration-driven construction**
|
|
328
|
+
Runtime behaviour is determined through configuration rather than hardcoded wiring.
|
|
329
|
+
|
|
330
|
+
- **Separation of concerns**
|
|
331
|
+
Discovery, configuration, resolution, construction, and application logic remain separate.
|
|
332
|
+
|
|
333
|
+
- **Strict typing**
|
|
334
|
+
Fully Pyright-clean and PEP 561 compliant.
|
|
335
|
+
|
|
336
|
+
- **Minimal implicit behaviour**
|
|
337
|
+
Runtime construction is deterministic and inspectable.
|
|
338
|
+
|
|
339
|
+
- **Composable services**
|
|
340
|
+
Runtime services are assembled from independent packages.
|
|
341
|
+
|
|
342
|
+
## Development
|
|
343
|
+
|
|
344
|
+
```bash
|
|
345
|
+
poetry install
|
|
346
|
+
|
|
347
|
+
make check
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
Run the RuntimeContext smoke test:
|
|
351
|
+
|
|
352
|
+
```bash
|
|
353
|
+
poetry run python scripts/smoke_runtime_context.py
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
## Status
|
|
357
|
+
|
|
358
|
+
Current release status:
|
|
359
|
+
|
|
360
|
+
```text
|
|
361
|
+
Runtime Identity Discovery Complete
|
|
362
|
+
RuntimeContext Construction Complete
|
|
363
|
+
Service Expansion In Progress
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
Current RuntimeContext materialises:
|
|
367
|
+
|
|
368
|
+
```text
|
|
369
|
+
configuration
|
|
370
|
+
secrets
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
Additional services will be added incrementally.
|
|
374
|
+
|
|
375
|
+
## License
|
|
376
|
+
|
|
377
|
+
MIT License. See [LICENSE](LICENSE).
|
|
378
|
+
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
# mxm-runtime
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+
[](https://microsoft.github.io/pyright/)
|
|
7
|
+
|
|
8
|
+
Runtime discovery, configuration-driven service construction, and RuntimeContext assembly for the Money Ex Machina ecosystem.
|
|
9
|
+
|
|
10
|
+
`mxm-runtime` is responsible for constructing the operational environment in which MXM applications execute.
|
|
11
|
+
|
|
12
|
+
It discovers runtime characteristics, loads and resolves configuration, constructs configured services, and assembles them into a single RuntimeContext.
|
|
13
|
+
|
|
14
|
+
## Purpose
|
|
15
|
+
|
|
16
|
+
MXM applications require more than configuration files.
|
|
17
|
+
|
|
18
|
+
They require an operational environment:
|
|
19
|
+
|
|
20
|
+
```text
|
|
21
|
+
Who am I?
|
|
22
|
+
Where am I running?
|
|
23
|
+
Which configuration applies?
|
|
24
|
+
Which services are available?
|
|
25
|
+
Where should data and artefacts live?
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`mxm-runtime` answers these questions.
|
|
29
|
+
|
|
30
|
+
It exists to separate:
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
application code
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
from:
|
|
37
|
+
|
|
38
|
+
```text
|
|
39
|
+
runtime discovery
|
|
40
|
+
configuration loading
|
|
41
|
+
service construction
|
|
42
|
+
deployment concerns
|
|
43
|
+
context assembly
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Applications should not:
|
|
47
|
+
|
|
48
|
+
- discover machine characteristics,
|
|
49
|
+
- determine deployment substrate,
|
|
50
|
+
- load configuration directly,
|
|
51
|
+
- construct service APIs,
|
|
52
|
+
- or reason about deployment topology.
|
|
53
|
+
|
|
54
|
+
Instead, applications receive a configured RuntimeContext:
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
context = build_runtime_context(
|
|
58
|
+
identity=runtime_identity,
|
|
59
|
+
)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
and consume the services and resources provided by that context.
|
|
63
|
+
|
|
64
|
+
## Architecture
|
|
65
|
+
|
|
66
|
+
`mxm-runtime` acts as the runtime constructor layer of the MXM architecture.
|
|
67
|
+
|
|
68
|
+
```text
|
|
69
|
+
RuntimeIdentity
|
|
70
|
+
↓
|
|
71
|
+
mxm-config
|
|
72
|
+
↓
|
|
73
|
+
Configuration Resolution
|
|
74
|
+
↓
|
|
75
|
+
Service Construction
|
|
76
|
+
↓
|
|
77
|
+
RuntimeContext
|
|
78
|
+
↓
|
|
79
|
+
Application
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The package owns runtime construction.
|
|
83
|
+
|
|
84
|
+
It does not own configuration semantics or secret resolution semantics.
|
|
85
|
+
|
|
86
|
+
Those responsibilities belong to:
|
|
87
|
+
|
|
88
|
+
```text
|
|
89
|
+
mxm-config
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
and:
|
|
93
|
+
|
|
94
|
+
```text
|
|
95
|
+
mxm-secrets
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
respectively.
|
|
99
|
+
|
|
100
|
+
## Core Concepts
|
|
101
|
+
|
|
102
|
+
### RuntimeIdentity
|
|
103
|
+
|
|
104
|
+
Represents the operational identity of a running process.
|
|
105
|
+
|
|
106
|
+
Example:
|
|
107
|
+
|
|
108
|
+
```text
|
|
109
|
+
app mxm-moneymachine
|
|
110
|
+
environment dev
|
|
111
|
+
machine bridge
|
|
112
|
+
substrate local-process
|
|
113
|
+
role marketdata
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Runtime identity determines which configuration layers are selected and which services are constructed.
|
|
117
|
+
|
|
118
|
+
### Machine
|
|
119
|
+
|
|
120
|
+
A machine identifies a machine-specific configuration profile.
|
|
121
|
+
|
|
122
|
+
Examples:
|
|
123
|
+
|
|
124
|
+
```text
|
|
125
|
+
bridge
|
|
126
|
+
monolith
|
|
127
|
+
wildling
|
|
128
|
+
scribe
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Machine values are derived from operating-system characteristics and are used to select machine-specific configuration.
|
|
132
|
+
|
|
133
|
+
They are configuration selectors rather than unique hardware identifiers.
|
|
134
|
+
|
|
135
|
+
### Substrate
|
|
136
|
+
|
|
137
|
+
Represents the execution substrate.
|
|
138
|
+
|
|
139
|
+
Examples:
|
|
140
|
+
|
|
141
|
+
```text
|
|
142
|
+
local-process
|
|
143
|
+
docker
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Substrate allows runtime construction to adapt to deployment environment differences.
|
|
147
|
+
|
|
148
|
+
### RuntimeContext
|
|
149
|
+
|
|
150
|
+
Represents the fully constructed operational environment.
|
|
151
|
+
|
|
152
|
+
Current fields:
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
RuntimeContext(
|
|
156
|
+
identity=...,
|
|
157
|
+
config=...,
|
|
158
|
+
secrets=...,
|
|
159
|
+
)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Future versions may additionally materialise:
|
|
163
|
+
|
|
164
|
+
```text
|
|
165
|
+
paths
|
|
166
|
+
databases
|
|
167
|
+
reference data services
|
|
168
|
+
storage services
|
|
169
|
+
execution services
|
|
170
|
+
reporting services
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Applications are expected to consume runtime services through RuntimeContext.
|
|
174
|
+
|
|
175
|
+
## Runtime Construction Flow
|
|
176
|
+
|
|
177
|
+
Runtime construction follows the sequence:
|
|
178
|
+
|
|
179
|
+
```text
|
|
180
|
+
RuntimeIdentity
|
|
181
|
+
↓
|
|
182
|
+
load_config(...)
|
|
183
|
+
↓
|
|
184
|
+
Configuration Views
|
|
185
|
+
↓
|
|
186
|
+
Service Construction
|
|
187
|
+
↓
|
|
188
|
+
RuntimeContext
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
For example:
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
context = build_runtime_context(
|
|
195
|
+
identity=identity,
|
|
196
|
+
)
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
which currently materialises:
|
|
200
|
+
|
|
201
|
+
```text
|
|
202
|
+
configuration
|
|
203
|
+
secret services
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
and returns a configured RuntimeContext.
|
|
207
|
+
|
|
208
|
+
## Runtime Discovery
|
|
209
|
+
|
|
210
|
+
`mxm-runtime` provides discovery utilities for determining runtime characteristics.
|
|
211
|
+
|
|
212
|
+
Examples:
|
|
213
|
+
|
|
214
|
+
```python
|
|
215
|
+
machine = discover_machine()
|
|
216
|
+
substrate = discover_substrate()
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
These functions derive MXM runtime selectors from operating-system facts.
|
|
220
|
+
|
|
221
|
+
The resulting values are suitable for configuration resolution and runtime construction.
|
|
222
|
+
|
|
223
|
+
## Relationship To mxm-config
|
|
224
|
+
|
|
225
|
+
`mxm-config` owns:
|
|
226
|
+
|
|
227
|
+
```text
|
|
228
|
+
configuration storage
|
|
229
|
+
configuration loading
|
|
230
|
+
configuration merging
|
|
231
|
+
configuration views
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
`mxm-runtime` consumes configuration and constructs runtime services from it.
|
|
235
|
+
|
|
236
|
+
Example:
|
|
237
|
+
|
|
238
|
+
```text
|
|
239
|
+
RuntimeIdentity
|
|
240
|
+
↓
|
|
241
|
+
mxm-config
|
|
242
|
+
↓
|
|
243
|
+
MXMConfig
|
|
244
|
+
↓
|
|
245
|
+
RuntimeContext
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## Relationship To mxm-secrets
|
|
249
|
+
|
|
250
|
+
`mxm-secrets` owns:
|
|
251
|
+
|
|
252
|
+
```text
|
|
253
|
+
secret references
|
|
254
|
+
authorization
|
|
255
|
+
resolution
|
|
256
|
+
retrieval
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
`mxm-runtime` constructs configured secret services and makes them available through RuntimeContext.
|
|
260
|
+
|
|
261
|
+
Applications are expected to consume:
|
|
262
|
+
|
|
263
|
+
```python
|
|
264
|
+
context.secrets
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
rather than constructing SecretsApi instances directly.
|
|
268
|
+
|
|
269
|
+
## Installation
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
pip install mxm-runtime
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## Usage
|
|
276
|
+
|
|
277
|
+
Construct a RuntimeContext:
|
|
278
|
+
|
|
279
|
+
```python
|
|
280
|
+
from mxm.runtime import build_runtime_context
|
|
281
|
+
|
|
282
|
+
context = build_runtime_context(
|
|
283
|
+
identity=identity,
|
|
284
|
+
)
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Access configured services:
|
|
288
|
+
|
|
289
|
+
```python
|
|
290
|
+
api_key = context.secrets.get_secret(
|
|
291
|
+
"databento_api_key",
|
|
292
|
+
identity=context.identity,
|
|
293
|
+
)
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
## Design Principles
|
|
297
|
+
|
|
298
|
+
- **Explicit runtime identity**
|
|
299
|
+
Runtime identity is always represented explicitly.
|
|
300
|
+
|
|
301
|
+
- **Configuration-driven construction**
|
|
302
|
+
Runtime behaviour is determined through configuration rather than hardcoded wiring.
|
|
303
|
+
|
|
304
|
+
- **Separation of concerns**
|
|
305
|
+
Discovery, configuration, resolution, construction, and application logic remain separate.
|
|
306
|
+
|
|
307
|
+
- **Strict typing**
|
|
308
|
+
Fully Pyright-clean and PEP 561 compliant.
|
|
309
|
+
|
|
310
|
+
- **Minimal implicit behaviour**
|
|
311
|
+
Runtime construction is deterministic and inspectable.
|
|
312
|
+
|
|
313
|
+
- **Composable services**
|
|
314
|
+
Runtime services are assembled from independent packages.
|
|
315
|
+
|
|
316
|
+
## Development
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
poetry install
|
|
320
|
+
|
|
321
|
+
make check
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
Run the RuntimeContext smoke test:
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
poetry run python scripts/smoke_runtime_context.py
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## Status
|
|
331
|
+
|
|
332
|
+
Current release status:
|
|
333
|
+
|
|
334
|
+
```text
|
|
335
|
+
Runtime Identity Discovery Complete
|
|
336
|
+
RuntimeContext Construction Complete
|
|
337
|
+
Service Expansion In Progress
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Current RuntimeContext materialises:
|
|
341
|
+
|
|
342
|
+
```text
|
|
343
|
+
configuration
|
|
344
|
+
secrets
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Additional services will be added incrementally.
|
|
348
|
+
|
|
349
|
+
## License
|
|
350
|
+
|
|
351
|
+
MIT License. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "mxm-runtime"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Runtime identity discovery and runtime context construction for Money Ex Machina."
|
|
5
|
+
keywords = [
|
|
6
|
+
"runtime",
|
|
7
|
+
"context",
|
|
8
|
+
"identity",
|
|
9
|
+
"configuration",
|
|
10
|
+
"deployment",
|
|
11
|
+
"infrastructure",
|
|
12
|
+
"orchestration",
|
|
13
|
+
"prefect",
|
|
14
|
+
"mxm",
|
|
15
|
+
]
|
|
16
|
+
authors = ["mxm <contact@moneyexmachina.com>"]
|
|
17
|
+
license = "MIT"
|
|
18
|
+
readme = "README.md"
|
|
19
|
+
packages = [{ include = "mxm", from = "src" }]
|
|
20
|
+
homepage = "https://github.com/moneyexmachina/mxm-runtime"
|
|
21
|
+
repository = "https://github.com/moneyexmachina/mxm-runtime"
|
|
22
|
+
include = ["src/mxm/runtime/py.typed"]
|
|
23
|
+
classifiers = [
|
|
24
|
+
"Development Status :: 3 - Alpha",
|
|
25
|
+
"Intended Audience :: Developers",
|
|
26
|
+
"License :: OSI Approved :: MIT License",
|
|
27
|
+
"Programming Language :: Python :: 3.13",
|
|
28
|
+
"Typing :: Typed",
|
|
29
|
+
]
|
|
30
|
+
[tool.poetry.dependencies]
|
|
31
|
+
python = ">=3.13,<3.15"
|
|
32
|
+
mxm-types = "^0.3.1"
|
|
33
|
+
mxm-config = ">=0.6.2, <0.7"
|
|
34
|
+
mxm-secrets = ">=0.3.2, <0.4"
|
|
35
|
+
typer = "^0.20.0"
|
|
36
|
+
rich = "^14.2.0"
|
|
37
|
+
|
|
38
|
+
[tool.poetry.scripts]
|
|
39
|
+
|
|
40
|
+
[tool.poetry.group.dev.dependencies]
|
|
41
|
+
pytest = "^8.4.2"
|
|
42
|
+
ruff = "^0.14.3"
|
|
43
|
+
black = "^25.9.0"
|
|
44
|
+
isort = "^7.0.0"
|
|
45
|
+
pyright = "^1.1.407"
|
|
46
|
+
|
|
47
|
+
[build-system]
|
|
48
|
+
requires = ["poetry-core>=2.0.0"]
|
|
49
|
+
build-backend = "poetry.core.masonry.api"
|
|
50
|
+
|
|
51
|
+
# ---- Tooling (package-local, MXM defaults) ----
|
|
52
|
+
[tool.black]
|
|
53
|
+
line-length = 88
|
|
54
|
+
target-version = ["py313"]
|
|
55
|
+
|
|
56
|
+
[tool.isort]
|
|
57
|
+
profile = "black"
|
|
58
|
+
line_length = 88
|
|
59
|
+
known_first_party = ["mxm"]
|
|
60
|
+
combine_as_imports = true
|
|
61
|
+
|
|
62
|
+
[tool.ruff]
|
|
63
|
+
line-length = 88
|
|
64
|
+
target-version = "py313"
|
|
65
|
+
|
|
66
|
+
[tool.ruff.lint]
|
|
67
|
+
select = ["E","F","I","B","UP","C90","RUF"]
|
|
68
|
+
ignore = ["E203","E501"]
|
|
69
|
+
|
|
70
|
+
[tool.ruff.lint.isort]
|
|
71
|
+
known-first-party = ["mxm"]
|
|
72
|
+
|
|
73
|
+
[tool.pytest.ini_options]
|
|
74
|
+
addopts = "-q"
|
|
75
|
+
pythonpath = ["src"]
|
|
76
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Runtime identity discovery and runtime context construction for MXM."""
|
|
2
|
+
|
|
3
|
+
from mxm.runtime.identity import build_runtime_identity
|
|
4
|
+
from mxm.runtime.validation import RuntimeIdentityError, validate_runtime_identity_shape
|
|
5
|
+
from mxm.types import RuntimeIdentity
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"RuntimeIdentity",
|
|
9
|
+
"RuntimeIdentityError",
|
|
10
|
+
"build_runtime_identity",
|
|
11
|
+
"validate_runtime_identity_shape",
|
|
12
|
+
]
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Runtime identity attestation.
|
|
2
|
+
|
|
3
|
+
Attestation checks whether the empirical claims in a RuntimeIdentity match the
|
|
4
|
+
current running process.
|
|
5
|
+
|
|
6
|
+
This is distinct from shape validation. Shape validation checks whether an
|
|
7
|
+
identity object is well formed. Attestation checks whether selected claims are
|
|
8
|
+
true here.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from mxm.runtime.discovery import discover_machine, discover_substrate
|
|
14
|
+
from mxm.types import RuntimeIdentity
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class RuntimeIdentityAttestationError(RuntimeError):
|
|
18
|
+
"""Raised when a runtime identity is not true for this process."""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def attest_runtime_identity(identity: RuntimeIdentity) -> None:
|
|
22
|
+
"""Attest that empirical RuntimeIdentity claims match local discovery.
|
|
23
|
+
|
|
24
|
+
Only locally discoverable empirical fields are attested:
|
|
25
|
+
|
|
26
|
+
- ``machine``
|
|
27
|
+
- ``substrate``
|
|
28
|
+
|
|
29
|
+
The following fields are not attested here because they are invocation or
|
|
30
|
+
deployment claims rather than directly discoverable local facts:
|
|
31
|
+
|
|
32
|
+
- ``app``
|
|
33
|
+
- ``environment``
|
|
34
|
+
- ``role``
|
|
35
|
+
"""
|
|
36
|
+
discovered_machine = discover_machine()
|
|
37
|
+
discovered_substrate = discover_substrate()
|
|
38
|
+
|
|
39
|
+
errors: list[str] = []
|
|
40
|
+
|
|
41
|
+
if identity.machine != discovered_machine:
|
|
42
|
+
errors.append(
|
|
43
|
+
"machine claim does not match local discovery: "
|
|
44
|
+
f"identity.machine={identity.machine!r}, "
|
|
45
|
+
f"discovered_machine={discovered_machine!r}"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
if identity.substrate != discovered_substrate:
|
|
49
|
+
errors.append(
|
|
50
|
+
"substrate claim does not match local discovery: "
|
|
51
|
+
f"identity.substrate={identity.substrate!r}, "
|
|
52
|
+
f"discovered_substrate={discovered_substrate!r}"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
if errors:
|
|
56
|
+
message = "; ".join(errors)
|
|
57
|
+
raise RuntimeIdentityAttestationError(message)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""RuntimeContext construction for mxm-runtime.
|
|
2
|
+
|
|
3
|
+
This module owns materialisation of a RuntimeContext from an explicit
|
|
4
|
+
RuntimeIdentity and resolved MXM configuration.
|
|
5
|
+
|
|
6
|
+
It deliberately keeps package responsibilities separated:
|
|
7
|
+
|
|
8
|
+
- mxm-config loads, slices, and converts configuration.
|
|
9
|
+
- mxm-secrets constructs the configured SecretsApi from plain config data.
|
|
10
|
+
- mxm-runtime assembles the materialised RuntimeContext.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
from mxm.config import load_config, make_view, to_config_data
|
|
18
|
+
from mxm.runtime.context import RuntimeContext
|
|
19
|
+
from mxm.secrets import SecretsApi
|
|
20
|
+
from mxm.types import RuntimeIdentity
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def build_runtime_context(
|
|
24
|
+
*,
|
|
25
|
+
identity: RuntimeIdentity,
|
|
26
|
+
store_root: Path | None = None,
|
|
27
|
+
) -> RuntimeContext:
|
|
28
|
+
"""Build a RuntimeContext for an explicit runtime identity.
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
identity
|
|
33
|
+
Runtime identity used to resolve configuration and materialise runtime
|
|
34
|
+
services.
|
|
35
|
+
store_root
|
|
36
|
+
Optional configuration store root. If omitted, mxm-config uses its
|
|
37
|
+
default configuration store location.
|
|
38
|
+
|
|
39
|
+
Returns
|
|
40
|
+
-------
|
|
41
|
+
RuntimeContext
|
|
42
|
+
Materialised runtime context containing the supplied identity, resolved
|
|
43
|
+
configuration, and configured SecretsApi.
|
|
44
|
+
|
|
45
|
+
Raises
|
|
46
|
+
------
|
|
47
|
+
FileNotFoundError
|
|
48
|
+
If required configuration files are missing.
|
|
49
|
+
KeyError
|
|
50
|
+
If selected configuration layers or required config sections are absent.
|
|
51
|
+
TypeError
|
|
52
|
+
If configuration sections have invalid structure.
|
|
53
|
+
ValueError
|
|
54
|
+
If secrets configuration fails mxm-secrets validation.
|
|
55
|
+
"""
|
|
56
|
+
if store_root is None:
|
|
57
|
+
config = load_config(identity=identity)
|
|
58
|
+
else:
|
|
59
|
+
config = load_config(
|
|
60
|
+
identity=identity,
|
|
61
|
+
store_root=store_root,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
secrets_config = make_view(
|
|
65
|
+
config,
|
|
66
|
+
"mxm_secrets",
|
|
67
|
+
readonly=True,
|
|
68
|
+
resolve=True,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
secrets = SecretsApi.from_config_data(
|
|
72
|
+
to_config_data(secrets_config),
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
return RuntimeContext(
|
|
76
|
+
identity=identity,
|
|
77
|
+
config=config,
|
|
78
|
+
secrets=secrets,
|
|
79
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from mxm.config import MXMConfig
|
|
7
|
+
from mxm.secrets import SecretsApi
|
|
8
|
+
from mxm.types import RuntimeIdentity
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True, slots=True)
|
|
12
|
+
class RuntimePaths:
|
|
13
|
+
"""Resolved filesystem locations available to this runtime."""
|
|
14
|
+
|
|
15
|
+
data_root: Path | None = None
|
|
16
|
+
artifact_root: Path | None = None
|
|
17
|
+
state_root: Path | None = None
|
|
18
|
+
log_root: Path | None = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True, slots=True)
|
|
22
|
+
class RuntimeMetadata:
|
|
23
|
+
"""Materialised metadata about the current execution substrate."""
|
|
24
|
+
|
|
25
|
+
substrate: str
|
|
26
|
+
is_container: bool
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True, slots=True)
|
|
30
|
+
class RuntimeContext:
|
|
31
|
+
"""Materialised operational context for an MXM runtime."""
|
|
32
|
+
|
|
33
|
+
identity: RuntimeIdentity
|
|
34
|
+
config: MXMConfig
|
|
35
|
+
secrets: SecretsApi | None = None
|
|
36
|
+
paths: RuntimePaths | None = None
|
|
37
|
+
runtime: RuntimeMetadata | None = None
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Runtime discovery helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import socket
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
DEFAULT_DOCKERENV_PATH = Path("/.dockerenv")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def discover_machine() -> str:
|
|
12
|
+
"""Discover the current MXM machine selector.
|
|
13
|
+
|
|
14
|
+
The machine selector identifies the machine-specific configuration profile
|
|
15
|
+
that should be applied by MXM.
|
|
16
|
+
|
|
17
|
+
It is derived from operating-system host information but is not required to
|
|
18
|
+
equal the raw hostname reported by the operating system. The purpose of the
|
|
19
|
+
selector is configuration selection rather than unique host identification.
|
|
20
|
+
|
|
21
|
+
For v0.1, the selector is derived from the local hostname by taking the
|
|
22
|
+
unqualified hostname component.
|
|
23
|
+
|
|
24
|
+
Examples
|
|
25
|
+
--------
|
|
26
|
+
Raw hostname:
|
|
27
|
+
|
|
28
|
+
bridge.local
|
|
29
|
+
|
|
30
|
+
Produces:
|
|
31
|
+
|
|
32
|
+
bridge
|
|
33
|
+
|
|
34
|
+
Raw hostname:
|
|
35
|
+
|
|
36
|
+
monolith
|
|
37
|
+
|
|
38
|
+
Produces:
|
|
39
|
+
|
|
40
|
+
monolith
|
|
41
|
+
|
|
42
|
+
Returns
|
|
43
|
+
-------
|
|
44
|
+
str
|
|
45
|
+
MXM machine selector used for machine-specific configuration loading.
|
|
46
|
+
"""
|
|
47
|
+
hostname = socket.gethostname()
|
|
48
|
+
return hostname.split(".", maxsplit=1)[0]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def discover_substrate(
|
|
52
|
+
*,
|
|
53
|
+
dockerenv_path: Path = DEFAULT_DOCKERENV_PATH,
|
|
54
|
+
) -> str:
|
|
55
|
+
"""Discover the current execution substrate.
|
|
56
|
+
|
|
57
|
+
Discovery order:
|
|
58
|
+
|
|
59
|
+
1. Docker marker file.
|
|
60
|
+
2. ``local-process`` fallback.
|
|
61
|
+
|
|
62
|
+
The Docker marker path is injectable because ``/.dockerenv`` is a
|
|
63
|
+
conventional probe rather than an MXM-owned runtime concept.
|
|
64
|
+
"""
|
|
65
|
+
if dockerenv_path.exists():
|
|
66
|
+
return "docker-container"
|
|
67
|
+
|
|
68
|
+
return "local-process"
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Runtime identity construction.
|
|
2
|
+
|
|
3
|
+
This module provides the small public construction helper for MXM runtime
|
|
4
|
+
identity. It deliberately keeps discovery and validation at the boundary:
|
|
5
|
+
callers may pass explicit values, while missing machine/substrate values are
|
|
6
|
+
delegated to discovery helpers.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from mxm.runtime.discovery import discover_machine, discover_substrate
|
|
12
|
+
from mxm.runtime.validation import validate_runtime_identity_shape
|
|
13
|
+
from mxm.types import (
|
|
14
|
+
RuntimeIdentity,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def build_runtime_identity(
|
|
19
|
+
*,
|
|
20
|
+
app: str,
|
|
21
|
+
environment: str,
|
|
22
|
+
role: str,
|
|
23
|
+
machine: str | None = None,
|
|
24
|
+
substrate: str | None = None,
|
|
25
|
+
) -> RuntimeIdentity:
|
|
26
|
+
"""Build and validate a RuntimeIdentity.
|
|
27
|
+
|
|
28
|
+
Parameters
|
|
29
|
+
----------
|
|
30
|
+
app:
|
|
31
|
+
MXM application or package identifier.
|
|
32
|
+
environment:
|
|
33
|
+
Operational environment, for example ``dev``, ``test``, or ``prod``.
|
|
34
|
+
role:
|
|
35
|
+
Runtime responsibility, for example ``research`` or ``marketdata``.
|
|
36
|
+
machine:
|
|
37
|
+
Physical or logical host identifier. If omitted, this is discovered.
|
|
38
|
+
substrate:
|
|
39
|
+
Execution substrate. If omitted, this is discovered.
|
|
40
|
+
|
|
41
|
+
Returns
|
|
42
|
+
-------
|
|
43
|
+
RuntimeIdentity
|
|
44
|
+
A validated runtime identity using the shared mxm-types vocabulary.
|
|
45
|
+
"""
|
|
46
|
+
resolved_machine = machine if machine is not None else discover_machine()
|
|
47
|
+
resolved_substrate = substrate if substrate is not None else discover_substrate()
|
|
48
|
+
|
|
49
|
+
identity = RuntimeIdentity(
|
|
50
|
+
app=app,
|
|
51
|
+
environment=environment,
|
|
52
|
+
machine=resolved_machine,
|
|
53
|
+
substrate=resolved_substrate,
|
|
54
|
+
role=role,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
validate_runtime_identity_shape(identity)
|
|
58
|
+
|
|
59
|
+
return identity
|
|
File without changes
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Runtime identity validation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from mxm.types import RuntimeIdentity
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class RuntimeIdentityError(ValueError):
|
|
9
|
+
"""Raised when a runtime identity is invalid."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def validate_runtime_identity_shape(identity: RuntimeIdentity) -> None:
|
|
13
|
+
"""Validate a RuntimeIdentity.
|
|
14
|
+
|
|
15
|
+
Validation is intentionally minimal for v0.1.0. Policy-specific allowed
|
|
16
|
+
values should be introduced only once the deployment model has stabilized.
|
|
17
|
+
"""
|
|
18
|
+
fields = {
|
|
19
|
+
"app": identity.app,
|
|
20
|
+
"environment": identity.environment,
|
|
21
|
+
"machine": identity.machine,
|
|
22
|
+
"substrate": identity.substrate,
|
|
23
|
+
"role": identity.role,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
missing = [name for name, value in fields.items() if not str(value).strip()]
|
|
27
|
+
|
|
28
|
+
if missing:
|
|
29
|
+
joined = ", ".join(missing)
|
|
30
|
+
raise RuntimeIdentityError(
|
|
31
|
+
f"RuntimeIdentity contains empty required field(s): {joined}"
|
|
32
|
+
)
|