astraform-remote-domain-author-kit 0.1.2__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.
- astraform_remote_domain_author_kit-0.1.2/PKG-INFO +329 -0
- astraform_remote_domain_author_kit-0.1.2/README.md +307 -0
- astraform_remote_domain_author_kit-0.1.2/pyproject.toml +58 -0
- astraform_remote_domain_author_kit-0.1.2/setup.cfg +4 -0
- astraform_remote_domain_author_kit-0.1.2/src/astraform/remote_domain/author_kit/__init__.py +63 -0
- astraform_remote_domain_author_kit-0.1.2/src/astraform/remote_domain/author_kit/fastapi.py +233 -0
- astraform_remote_domain_author_kit-0.1.2/src/astraform/remote_domain/author_kit/protocol.py +757 -0
- astraform_remote_domain_author_kit-0.1.2/src/astraform/remote_domain/author_kit/templates/fastapi-minimal/README.md +115 -0
- astraform_remote_domain_author_kit-0.1.2/src/astraform/remote_domain/author_kit/templates/fastapi-minimal/pyproject.toml +31 -0
- astraform_remote_domain_author_kit-0.1.2/src/astraform/remote_domain/author_kit/templates/fastapi-minimal/scripts/bootstrap-local-sdk.sh +37 -0
- astraform_remote_domain_author_kit-0.1.2/src/astraform/remote_domain/author_kit/templates/fastapi-minimal/src/acme_remote_domain/__init__.py +1 -0
- astraform_remote_domain_author_kit-0.1.2/src/astraform/remote_domain/author_kit/templates/fastapi-minimal/src/acme_remote_domain/main.py +12 -0
- astraform_remote_domain_author_kit-0.1.2/src/astraform/remote_domain/author_kit/templates/fastapi-minimal/src/acme_remote_domain/population_catalog.json +69 -0
- astraform_remote_domain_author_kit-0.1.2/src/astraform/remote_domain/author_kit/templates/fastapi-minimal/src/acme_remote_domain/service.py +332 -0
- astraform_remote_domain_author_kit-0.1.2/src/astraform/remote_domain/author_kit/templates/fastapi-minimal/src/acme_remote_domain/validate_population_catalog.py +49 -0
- astraform_remote_domain_author_kit-0.1.2/src/astraform/remote_domain/author_kit/templates/fastapi-minimal/tests/test_app.py +59 -0
- astraform_remote_domain_author_kit-0.1.2/src/astraform/remote_domain/author_kit/templates.py +90 -0
- astraform_remote_domain_author_kit-0.1.2/src/astraform_remote_domain_author_kit.egg-info/PKG-INFO +329 -0
- astraform_remote_domain_author_kit-0.1.2/src/astraform_remote_domain_author_kit.egg-info/SOURCES.txt +24 -0
- astraform_remote_domain_author_kit-0.1.2/src/astraform_remote_domain_author_kit.egg-info/dependency_links.txt +1 -0
- astraform_remote_domain_author_kit-0.1.2/src/astraform_remote_domain_author_kit.egg-info/entry_points.txt +2 -0
- astraform_remote_domain_author_kit-0.1.2/src/astraform_remote_domain_author_kit.egg-info/requires.txt +8 -0
- astraform_remote_domain_author_kit-0.1.2/src/astraform_remote_domain_author_kit.egg-info/top_level.txt +1 -0
- astraform_remote_domain_author_kit-0.1.2/tests/test_fastapi.py +325 -0
- astraform_remote_domain_author_kit-0.1.2/tests/test_protocol.py +364 -0
- astraform_remote_domain_author_kit-0.1.2/tests/test_templates.py +55 -0
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: astraform-remote-domain-author-kit
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: Python author kit for Astraform remote-domain.v1 services
|
|
5
|
+
License-Expression: Apache-2.0
|
|
6
|
+
Project-URL: Documentation, https://github.com/astraform/platform/blob/main/docs/guides/remote-domain-authoring-guide.md
|
|
7
|
+
Project-URL: Source, https://github.com/astraform/platform
|
|
8
|
+
Project-URL: Publishing, https://github.com/astraform/platform/blob/main/docs/platform/remote-domain-sdk-publishing.md
|
|
9
|
+
Keywords: astraform,remote-domain,simulation,conformance
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Framework :: FastAPI
|
|
13
|
+
Classifier: Typing :: Typed
|
|
14
|
+
Requires-Python: >=3.12
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
Provides-Extra: fastapi
|
|
17
|
+
Requires-Dist: fastapi<1.0,>=0.115; extra == "fastapi"
|
|
18
|
+
Provides-Extra: test
|
|
19
|
+
Requires-Dist: fastapi<1.0,>=0.115; extra == "test"
|
|
20
|
+
Requires-Dist: httpx<1.0,>=0.28; extra == "test"
|
|
21
|
+
Requires-Dist: pytest<9.0,>=8.3; extra == "test"
|
|
22
|
+
|
|
23
|
+
# astraform-remote-domain-author-kit
|
|
24
|
+
|
|
25
|
+
This is the Python author kit for `remote-domain.v1`.
|
|
26
|
+
|
|
27
|
+
Brutal truth: an "SDK" alone is not enough. If teams still have to reverse
|
|
28
|
+
engineer request envelopes, invent their own starter layout, or guess how to
|
|
29
|
+
prove conformance, you did not ship onboarding. You shipped homework.
|
|
30
|
+
|
|
31
|
+
This author kit is broader than a thin SDK. It includes:
|
|
32
|
+
|
|
33
|
+
- reusable protocol constants and envelope builders
|
|
34
|
+
- request validation helpers
|
|
35
|
+
- an optional FastAPI app factory
|
|
36
|
+
- a starter template inside the package
|
|
37
|
+
|
|
38
|
+
If you only want helper functions, that is the SDK-like layer. The author kit
|
|
39
|
+
is the whole package around it.
|
|
40
|
+
|
|
41
|
+
## Install
|
|
42
|
+
|
|
43
|
+
Current status: the published PyPI baseline is
|
|
44
|
+
`astraform-remote-domain-author-kit==0.1.0`. The workspace release target is
|
|
45
|
+
`0.1.2`, which adds the current Population Builder catalog/readiness starter
|
|
46
|
+
shape. Do not claim external Python catalog authoring support until `0.1.2` is
|
|
47
|
+
actually published to PyPI.
|
|
48
|
+
|
|
49
|
+
Protocol helpers only:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install astraform-remote-domain-author-kit==0.1.0
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
FastAPI helper included:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pip install 'astraform-remote-domain-author-kit[fastapi]==0.1.0'
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
For catalog-enabled `0.1.2` work before publication, use the monorepo editable
|
|
62
|
+
install or a private pre-release wheel.
|
|
63
|
+
|
|
64
|
+
Repo-local development:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install -e './remote-domain-author-kit-python[fastapi,test]'
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Starter-template validation is part of the release gate. It still runs the
|
|
71
|
+
template's `scripts/bootstrap-local-sdk.sh` path against the repo-local SDK
|
|
72
|
+
source, then separately runs an external-consumer smoke from the exact built
|
|
73
|
+
local author-kit and conformance wheels. That smoke creates a clean project,
|
|
74
|
+
copies the starter through the installed `astraform-remote-domain-copy-starter`
|
|
75
|
+
command, installs the copied starter with `--no-deps`, runs
|
|
76
|
+
`validate-population-catalog`, and passes
|
|
77
|
+
`remote-domain-conformance --population-catalog`. That keeps the source
|
|
78
|
+
bootstrap and release artifact paths honest before the matching SDK version is
|
|
79
|
+
visible on PyPI.
|
|
80
|
+
|
|
81
|
+
## Core Usage
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
from astraform.remote_domain.author_kit.protocol import build_manifest
|
|
85
|
+
from astraform.remote_domain.author_kit.protocol import build_projection
|
|
86
|
+
from astraform.remote_domain.author_kit.protocol import opaque_state
|
|
87
|
+
from astraform.remote_domain.author_kit.protocol import projection_envelope
|
|
88
|
+
from astraform.remote_domain.author_kit.protocol import success_envelope
|
|
89
|
+
from astraform.remote_domain.author_kit.protocol import validate_request_envelope
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def manifest() -> dict:
|
|
93
|
+
return build_manifest(
|
|
94
|
+
domain_id="acme-ops",
|
|
95
|
+
display_name="Acme Operations Domain",
|
|
96
|
+
description="Remote proof domain",
|
|
97
|
+
schema_version="acme-ops.state.v1",
|
|
98
|
+
supported_agent_types=["Operator"],
|
|
99
|
+
supported_interaction_modes=["SIMULATION", "HYBRID"],
|
|
100
|
+
tools=[
|
|
101
|
+
{
|
|
102
|
+
"name": "lookup_case",
|
|
103
|
+
"description": "Look up a case in the remote domain.",
|
|
104
|
+
"inputSchema": {"type": "object", "additionalProperties": False},
|
|
105
|
+
}
|
|
106
|
+
],
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def prepare(request: dict) -> dict:
|
|
111
|
+
validate_request_envelope(
|
|
112
|
+
request,
|
|
113
|
+
expected_domain_id="acme-ops",
|
|
114
|
+
expected_operation="prepare",
|
|
115
|
+
require_idempotency=True,
|
|
116
|
+
)
|
|
117
|
+
state = opaque_state(
|
|
118
|
+
"acme-ops.state.v1",
|
|
119
|
+
{"personaName": "Taylor", "completedWorkCount": 0},
|
|
120
|
+
)
|
|
121
|
+
projection = build_projection(
|
|
122
|
+
runtime_metadata={"domainProfile": "acme"},
|
|
123
|
+
status_view={"completedWorkCount": 0},
|
|
124
|
+
inspection_view={"tasks": []},
|
|
125
|
+
)
|
|
126
|
+
return success_envelope(
|
|
127
|
+
request,
|
|
128
|
+
runtime_identity="acme-ops::Taylor",
|
|
129
|
+
next_state=state,
|
|
130
|
+
projection=projection,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def status(request: dict) -> dict:
|
|
135
|
+
validate_request_envelope(
|
|
136
|
+
request,
|
|
137
|
+
expected_domain_id="acme-ops",
|
|
138
|
+
expected_operation="status",
|
|
139
|
+
require_state=True,
|
|
140
|
+
)
|
|
141
|
+
return projection_envelope(
|
|
142
|
+
request,
|
|
143
|
+
projection=build_projection(
|
|
144
|
+
runtime_metadata={"domainProfile": "acme"},
|
|
145
|
+
status_view={"completedWorkCount": 0},
|
|
146
|
+
inspection_view={"tasks": []},
|
|
147
|
+
),
|
|
148
|
+
)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Domain providers may add optional `evidence_events=[...]` to
|
|
152
|
+
`success_envelope(...)` or `projection_envelope(...)`. Use that lane for
|
|
153
|
+
provider-internal evidence that Astraform cannot observe directly, such as a
|
|
154
|
+
private third-party call or domain-owned policy check.
|
|
155
|
+
|
|
156
|
+
## FastAPI App Factory
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
from astraform.remote_domain.author_kit.fastapi import create_fastapi_app
|
|
160
|
+
|
|
161
|
+
app = create_fastapi_app(
|
|
162
|
+
service=my_remote_domain_service,
|
|
163
|
+
policy_wind_tunnel_service=my_policy_wind_tunnel_service,
|
|
164
|
+
)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
The service object must implement:
|
|
168
|
+
|
|
169
|
+
- `manifest()`
|
|
170
|
+
- optional `population_catalog()` for
|
|
171
|
+
`GET /api/remote-domain/v1/population/catalog`
|
|
172
|
+
- `prepare(payload)`
|
|
173
|
+
- `execute_work(payload)`
|
|
174
|
+
- `status(payload)`
|
|
175
|
+
- `inspection(payload)`
|
|
176
|
+
- `shutdown(payload)`
|
|
177
|
+
|
|
178
|
+
`population_catalog()` must return `domain_population_catalog.v1`. The FastAPI
|
|
179
|
+
factory validates the catalog before returning it, so missing archetype fields,
|
|
180
|
+
bad recipe numbers, unknown archetype references, and malformed eligibility
|
|
181
|
+
fail before Population Builder treats the provider as usable.
|
|
182
|
+
|
|
183
|
+
Prefer a file-backed catalog over an inline Python dict. That keeps the domain
|
|
184
|
+
authoring surface reviewable and conformance-testable:
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
from importlib import resources
|
|
188
|
+
|
|
189
|
+
from astraform.remote_domain.author_kit import load_population_catalog
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def population_catalog() -> dict:
|
|
193
|
+
catalog_file = resources.files("my_remote_domain").joinpath("population_catalog.json")
|
|
194
|
+
return load_population_catalog(catalog_file)
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
`load_population_catalog(...)` reads JSON and runs the same
|
|
198
|
+
`validate_population_catalog(...)` checks used by the FastAPI route.
|
|
199
|
+
|
|
200
|
+
The bundled `fastapi-minimal` starter also installs a provider-local preflight:
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
validate-population-catalog
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
That command validates `src/acme_remote_domain/population_catalog.json` before
|
|
207
|
+
the provider starts and prints the exact rejected field when the catalog is
|
|
208
|
+
malformed. Treat it as the first command after editing archetypes, segment
|
|
209
|
+
templates, validation rules, or run-path eligibility.
|
|
210
|
+
|
|
211
|
+
The optional `policy_wind_tunnel_service` object exposes the remote Wind Tunnel
|
|
212
|
+
provider routes:
|
|
213
|
+
|
|
214
|
+
- `GET /policy-wind-tunnel/pack`
|
|
215
|
+
- `GET /policy-wind-tunnel/presets`
|
|
216
|
+
- `POST /policy-wind-tunnel/runs`
|
|
217
|
+
- `GET /policy-wind-tunnel/runs/{runId}`
|
|
218
|
+
- `POST /policy-wind-tunnel/runs/{runId}/lifecycle`
|
|
219
|
+
- `GET /policy-wind-tunnel/runs/{runId}/bundle`
|
|
220
|
+
- `GET /policy-wind-tunnel/runs/{runId}/artifacts/{artifactType}`
|
|
221
|
+
- `GET /policy-wind-tunnel/runs/{runId}/artifacts/{artifactType}/readiness`
|
|
222
|
+
- `GET /policy-wind-tunnel/runs/{runId}/evidence-pack`
|
|
223
|
+
- `GET /policy-wind-tunnel/runs/{runId}/evidence-pack/readiness`
|
|
224
|
+
|
|
225
|
+
The readiness routes are metadata-only checks for dashboard download UX. They
|
|
226
|
+
must prove the same provider-owned artifact/evidence-pack path is readable
|
|
227
|
+
without materializing the JSON body or ZIP archive.
|
|
228
|
+
|
|
229
|
+
## Policy Expressions
|
|
230
|
+
|
|
231
|
+
When a domain exposes Policy Wind Tunnel behavior, CEL-compatible
|
|
232
|
+
`PolicyExpression` definitions are the portable decision contract. They are for
|
|
233
|
+
decision gates, eligibility checks, thresholds, and conformance-testable public
|
|
234
|
+
rules.
|
|
235
|
+
|
|
236
|
+
They are not the execution engine. Keep state mutation, external calls,
|
|
237
|
+
proprietary scoring, and side effects inside your service implementation or
|
|
238
|
+
domain-owned execution target. Use the expression's `executionTarget` as a
|
|
239
|
+
stable provider-owned target name.
|
|
240
|
+
|
|
241
|
+
Python providers should publish the same `/policy-wind-tunnel/pack` metadata as
|
|
242
|
+
Java providers. The helper below builds the portable parts of that response:
|
|
243
|
+
|
|
244
|
+
```python
|
|
245
|
+
from astraform.remote_domain.author_kit import build_policy_expression
|
|
246
|
+
from astraform.remote_domain.author_kit import build_policy_wind_tunnel_pack_metadata
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def wind_tunnel_pack() -> dict:
|
|
250
|
+
return build_policy_wind_tunnel_pack_metadata(
|
|
251
|
+
capabilities={"remoteProviderReady": True, "evidenceExport": True},
|
|
252
|
+
control_schema={
|
|
253
|
+
"type": "object",
|
|
254
|
+
"properties": {
|
|
255
|
+
"blockerCount": {"type": "integer", "minimum": 0},
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
ui_schema={
|
|
259
|
+
"fields": [
|
|
260
|
+
{"name": "blockerCount", "widget": "number", "label": "Blockers"},
|
|
261
|
+
],
|
|
262
|
+
},
|
|
263
|
+
outcome_schema={
|
|
264
|
+
"schemaVersion": "domain_outcome_schema.v1",
|
|
265
|
+
"metricDefinitions": [
|
|
266
|
+
{
|
|
267
|
+
"metricId": "blockerCount",
|
|
268
|
+
"label": "Blockers",
|
|
269
|
+
"unit": "count",
|
|
270
|
+
"evidence": "domain-state",
|
|
271
|
+
},
|
|
272
|
+
],
|
|
273
|
+
},
|
|
274
|
+
domain_labels={
|
|
275
|
+
"actorSingular": "campaign",
|
|
276
|
+
"actorPlural": "campaigns",
|
|
277
|
+
"simulatedActorsLabel": "Simulated campaigns",
|
|
278
|
+
},
|
|
279
|
+
policy_expressions=[
|
|
280
|
+
build_policy_expression(
|
|
281
|
+
expression_id="acme.blockers.acceptable",
|
|
282
|
+
expression="metrics.blockerCount <= 10",
|
|
283
|
+
execution_target="acme-policy-service",
|
|
284
|
+
description="Blockers must stay under launch threshold.",
|
|
285
|
+
),
|
|
286
|
+
],
|
|
287
|
+
)
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
See
|
|
291
|
+
[`docs/architecture/scenario-lab-policy-expressions.md`](../docs/architecture/scenario-lab-policy-expressions.md)
|
|
292
|
+
for the contract shape and CEL subset.
|
|
293
|
+
|
|
294
|
+
## Starter Template
|
|
295
|
+
|
|
296
|
+
Copy the bundled FastAPI starter from the installed author kit:
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
astraform-remote-domain-copy-starter acme-remote-domain
|
|
300
|
+
cd acme-remote-domain
|
|
301
|
+
pip install --no-deps -e .
|
|
302
|
+
validate-population-catalog
|
|
303
|
+
remote-domain-conformance --app acme_remote_domain.main:app --domain-id acme-remote --population-catalog
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
The underlying template resource lives under:
|
|
307
|
+
|
|
308
|
+
- `astraform/remote_domain/author_kit/templates/fastapi-minimal`
|
|
309
|
+
|
|
310
|
+
It is intentionally boring. That is a feature. Teams need a truthful starting
|
|
311
|
+
point, not a framework demo that hides the protocol.
|
|
312
|
+
|
|
313
|
+
## Publishing Status
|
|
314
|
+
|
|
315
|
+
This package is released through the shared Python + Java SDK publishing process
|
|
316
|
+
documented in
|
|
317
|
+
[`docs/platform/remote-domain-sdk-publishing.md`](../docs/platform/remote-domain-sdk-publishing.md).
|
|
318
|
+
|
|
319
|
+
Release guardrail:
|
|
320
|
+
|
|
321
|
+
```bash
|
|
322
|
+
SDK_VERSION=0.1.2 scripts/check-remote-domain-sdk-release-gates.sh
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
PyPI artifacts are immutable. Do not rerun a publish for an already published
|
|
326
|
+
version; run smoke-only verification or bump the SDK version.
|
|
327
|
+
|
|
328
|
+
Public package note: PyPI distributions expose this SDK implementation. Keep
|
|
329
|
+
host runtime internals and domain-private logic out of this package.
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
# astraform-remote-domain-author-kit
|
|
2
|
+
|
|
3
|
+
This is the Python author kit for `remote-domain.v1`.
|
|
4
|
+
|
|
5
|
+
Brutal truth: an "SDK" alone is not enough. If teams still have to reverse
|
|
6
|
+
engineer request envelopes, invent their own starter layout, or guess how to
|
|
7
|
+
prove conformance, you did not ship onboarding. You shipped homework.
|
|
8
|
+
|
|
9
|
+
This author kit is broader than a thin SDK. It includes:
|
|
10
|
+
|
|
11
|
+
- reusable protocol constants and envelope builders
|
|
12
|
+
- request validation helpers
|
|
13
|
+
- an optional FastAPI app factory
|
|
14
|
+
- a starter template inside the package
|
|
15
|
+
|
|
16
|
+
If you only want helper functions, that is the SDK-like layer. The author kit
|
|
17
|
+
is the whole package around it.
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
Current status: the published PyPI baseline is
|
|
22
|
+
`astraform-remote-domain-author-kit==0.1.0`. The workspace release target is
|
|
23
|
+
`0.1.2`, which adds the current Population Builder catalog/readiness starter
|
|
24
|
+
shape. Do not claim external Python catalog authoring support until `0.1.2` is
|
|
25
|
+
actually published to PyPI.
|
|
26
|
+
|
|
27
|
+
Protocol helpers only:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install astraform-remote-domain-author-kit==0.1.0
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
FastAPI helper included:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install 'astraform-remote-domain-author-kit[fastapi]==0.1.0'
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
For catalog-enabled `0.1.2` work before publication, use the monorepo editable
|
|
40
|
+
install or a private pre-release wheel.
|
|
41
|
+
|
|
42
|
+
Repo-local development:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install -e './remote-domain-author-kit-python[fastapi,test]'
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Starter-template validation is part of the release gate. It still runs the
|
|
49
|
+
template's `scripts/bootstrap-local-sdk.sh` path against the repo-local SDK
|
|
50
|
+
source, then separately runs an external-consumer smoke from the exact built
|
|
51
|
+
local author-kit and conformance wheels. That smoke creates a clean project,
|
|
52
|
+
copies the starter through the installed `astraform-remote-domain-copy-starter`
|
|
53
|
+
command, installs the copied starter with `--no-deps`, runs
|
|
54
|
+
`validate-population-catalog`, and passes
|
|
55
|
+
`remote-domain-conformance --population-catalog`. That keeps the source
|
|
56
|
+
bootstrap and release artifact paths honest before the matching SDK version is
|
|
57
|
+
visible on PyPI.
|
|
58
|
+
|
|
59
|
+
## Core Usage
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from astraform.remote_domain.author_kit.protocol import build_manifest
|
|
63
|
+
from astraform.remote_domain.author_kit.protocol import build_projection
|
|
64
|
+
from astraform.remote_domain.author_kit.protocol import opaque_state
|
|
65
|
+
from astraform.remote_domain.author_kit.protocol import projection_envelope
|
|
66
|
+
from astraform.remote_domain.author_kit.protocol import success_envelope
|
|
67
|
+
from astraform.remote_domain.author_kit.protocol import validate_request_envelope
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def manifest() -> dict:
|
|
71
|
+
return build_manifest(
|
|
72
|
+
domain_id="acme-ops",
|
|
73
|
+
display_name="Acme Operations Domain",
|
|
74
|
+
description="Remote proof domain",
|
|
75
|
+
schema_version="acme-ops.state.v1",
|
|
76
|
+
supported_agent_types=["Operator"],
|
|
77
|
+
supported_interaction_modes=["SIMULATION", "HYBRID"],
|
|
78
|
+
tools=[
|
|
79
|
+
{
|
|
80
|
+
"name": "lookup_case",
|
|
81
|
+
"description": "Look up a case in the remote domain.",
|
|
82
|
+
"inputSchema": {"type": "object", "additionalProperties": False},
|
|
83
|
+
}
|
|
84
|
+
],
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def prepare(request: dict) -> dict:
|
|
89
|
+
validate_request_envelope(
|
|
90
|
+
request,
|
|
91
|
+
expected_domain_id="acme-ops",
|
|
92
|
+
expected_operation="prepare",
|
|
93
|
+
require_idempotency=True,
|
|
94
|
+
)
|
|
95
|
+
state = opaque_state(
|
|
96
|
+
"acme-ops.state.v1",
|
|
97
|
+
{"personaName": "Taylor", "completedWorkCount": 0},
|
|
98
|
+
)
|
|
99
|
+
projection = build_projection(
|
|
100
|
+
runtime_metadata={"domainProfile": "acme"},
|
|
101
|
+
status_view={"completedWorkCount": 0},
|
|
102
|
+
inspection_view={"tasks": []},
|
|
103
|
+
)
|
|
104
|
+
return success_envelope(
|
|
105
|
+
request,
|
|
106
|
+
runtime_identity="acme-ops::Taylor",
|
|
107
|
+
next_state=state,
|
|
108
|
+
projection=projection,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def status(request: dict) -> dict:
|
|
113
|
+
validate_request_envelope(
|
|
114
|
+
request,
|
|
115
|
+
expected_domain_id="acme-ops",
|
|
116
|
+
expected_operation="status",
|
|
117
|
+
require_state=True,
|
|
118
|
+
)
|
|
119
|
+
return projection_envelope(
|
|
120
|
+
request,
|
|
121
|
+
projection=build_projection(
|
|
122
|
+
runtime_metadata={"domainProfile": "acme"},
|
|
123
|
+
status_view={"completedWorkCount": 0},
|
|
124
|
+
inspection_view={"tasks": []},
|
|
125
|
+
),
|
|
126
|
+
)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Domain providers may add optional `evidence_events=[...]` to
|
|
130
|
+
`success_envelope(...)` or `projection_envelope(...)`. Use that lane for
|
|
131
|
+
provider-internal evidence that Astraform cannot observe directly, such as a
|
|
132
|
+
private third-party call or domain-owned policy check.
|
|
133
|
+
|
|
134
|
+
## FastAPI App Factory
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from astraform.remote_domain.author_kit.fastapi import create_fastapi_app
|
|
138
|
+
|
|
139
|
+
app = create_fastapi_app(
|
|
140
|
+
service=my_remote_domain_service,
|
|
141
|
+
policy_wind_tunnel_service=my_policy_wind_tunnel_service,
|
|
142
|
+
)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The service object must implement:
|
|
146
|
+
|
|
147
|
+
- `manifest()`
|
|
148
|
+
- optional `population_catalog()` for
|
|
149
|
+
`GET /api/remote-domain/v1/population/catalog`
|
|
150
|
+
- `prepare(payload)`
|
|
151
|
+
- `execute_work(payload)`
|
|
152
|
+
- `status(payload)`
|
|
153
|
+
- `inspection(payload)`
|
|
154
|
+
- `shutdown(payload)`
|
|
155
|
+
|
|
156
|
+
`population_catalog()` must return `domain_population_catalog.v1`. The FastAPI
|
|
157
|
+
factory validates the catalog before returning it, so missing archetype fields,
|
|
158
|
+
bad recipe numbers, unknown archetype references, and malformed eligibility
|
|
159
|
+
fail before Population Builder treats the provider as usable.
|
|
160
|
+
|
|
161
|
+
Prefer a file-backed catalog over an inline Python dict. That keeps the domain
|
|
162
|
+
authoring surface reviewable and conformance-testable:
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
from importlib import resources
|
|
166
|
+
|
|
167
|
+
from astraform.remote_domain.author_kit import load_population_catalog
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def population_catalog() -> dict:
|
|
171
|
+
catalog_file = resources.files("my_remote_domain").joinpath("population_catalog.json")
|
|
172
|
+
return load_population_catalog(catalog_file)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
`load_population_catalog(...)` reads JSON and runs the same
|
|
176
|
+
`validate_population_catalog(...)` checks used by the FastAPI route.
|
|
177
|
+
|
|
178
|
+
The bundled `fastapi-minimal` starter also installs a provider-local preflight:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
validate-population-catalog
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
That command validates `src/acme_remote_domain/population_catalog.json` before
|
|
185
|
+
the provider starts and prints the exact rejected field when the catalog is
|
|
186
|
+
malformed. Treat it as the first command after editing archetypes, segment
|
|
187
|
+
templates, validation rules, or run-path eligibility.
|
|
188
|
+
|
|
189
|
+
The optional `policy_wind_tunnel_service` object exposes the remote Wind Tunnel
|
|
190
|
+
provider routes:
|
|
191
|
+
|
|
192
|
+
- `GET /policy-wind-tunnel/pack`
|
|
193
|
+
- `GET /policy-wind-tunnel/presets`
|
|
194
|
+
- `POST /policy-wind-tunnel/runs`
|
|
195
|
+
- `GET /policy-wind-tunnel/runs/{runId}`
|
|
196
|
+
- `POST /policy-wind-tunnel/runs/{runId}/lifecycle`
|
|
197
|
+
- `GET /policy-wind-tunnel/runs/{runId}/bundle`
|
|
198
|
+
- `GET /policy-wind-tunnel/runs/{runId}/artifacts/{artifactType}`
|
|
199
|
+
- `GET /policy-wind-tunnel/runs/{runId}/artifacts/{artifactType}/readiness`
|
|
200
|
+
- `GET /policy-wind-tunnel/runs/{runId}/evidence-pack`
|
|
201
|
+
- `GET /policy-wind-tunnel/runs/{runId}/evidence-pack/readiness`
|
|
202
|
+
|
|
203
|
+
The readiness routes are metadata-only checks for dashboard download UX. They
|
|
204
|
+
must prove the same provider-owned artifact/evidence-pack path is readable
|
|
205
|
+
without materializing the JSON body or ZIP archive.
|
|
206
|
+
|
|
207
|
+
## Policy Expressions
|
|
208
|
+
|
|
209
|
+
When a domain exposes Policy Wind Tunnel behavior, CEL-compatible
|
|
210
|
+
`PolicyExpression` definitions are the portable decision contract. They are for
|
|
211
|
+
decision gates, eligibility checks, thresholds, and conformance-testable public
|
|
212
|
+
rules.
|
|
213
|
+
|
|
214
|
+
They are not the execution engine. Keep state mutation, external calls,
|
|
215
|
+
proprietary scoring, and side effects inside your service implementation or
|
|
216
|
+
domain-owned execution target. Use the expression's `executionTarget` as a
|
|
217
|
+
stable provider-owned target name.
|
|
218
|
+
|
|
219
|
+
Python providers should publish the same `/policy-wind-tunnel/pack` metadata as
|
|
220
|
+
Java providers. The helper below builds the portable parts of that response:
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
from astraform.remote_domain.author_kit import build_policy_expression
|
|
224
|
+
from astraform.remote_domain.author_kit import build_policy_wind_tunnel_pack_metadata
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def wind_tunnel_pack() -> dict:
|
|
228
|
+
return build_policy_wind_tunnel_pack_metadata(
|
|
229
|
+
capabilities={"remoteProviderReady": True, "evidenceExport": True},
|
|
230
|
+
control_schema={
|
|
231
|
+
"type": "object",
|
|
232
|
+
"properties": {
|
|
233
|
+
"blockerCount": {"type": "integer", "minimum": 0},
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
ui_schema={
|
|
237
|
+
"fields": [
|
|
238
|
+
{"name": "blockerCount", "widget": "number", "label": "Blockers"},
|
|
239
|
+
],
|
|
240
|
+
},
|
|
241
|
+
outcome_schema={
|
|
242
|
+
"schemaVersion": "domain_outcome_schema.v1",
|
|
243
|
+
"metricDefinitions": [
|
|
244
|
+
{
|
|
245
|
+
"metricId": "blockerCount",
|
|
246
|
+
"label": "Blockers",
|
|
247
|
+
"unit": "count",
|
|
248
|
+
"evidence": "domain-state",
|
|
249
|
+
},
|
|
250
|
+
],
|
|
251
|
+
},
|
|
252
|
+
domain_labels={
|
|
253
|
+
"actorSingular": "campaign",
|
|
254
|
+
"actorPlural": "campaigns",
|
|
255
|
+
"simulatedActorsLabel": "Simulated campaigns",
|
|
256
|
+
},
|
|
257
|
+
policy_expressions=[
|
|
258
|
+
build_policy_expression(
|
|
259
|
+
expression_id="acme.blockers.acceptable",
|
|
260
|
+
expression="metrics.blockerCount <= 10",
|
|
261
|
+
execution_target="acme-policy-service",
|
|
262
|
+
description="Blockers must stay under launch threshold.",
|
|
263
|
+
),
|
|
264
|
+
],
|
|
265
|
+
)
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
See
|
|
269
|
+
[`docs/architecture/scenario-lab-policy-expressions.md`](../docs/architecture/scenario-lab-policy-expressions.md)
|
|
270
|
+
for the contract shape and CEL subset.
|
|
271
|
+
|
|
272
|
+
## Starter Template
|
|
273
|
+
|
|
274
|
+
Copy the bundled FastAPI starter from the installed author kit:
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
astraform-remote-domain-copy-starter acme-remote-domain
|
|
278
|
+
cd acme-remote-domain
|
|
279
|
+
pip install --no-deps -e .
|
|
280
|
+
validate-population-catalog
|
|
281
|
+
remote-domain-conformance --app acme_remote_domain.main:app --domain-id acme-remote --population-catalog
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
The underlying template resource lives under:
|
|
285
|
+
|
|
286
|
+
- `astraform/remote_domain/author_kit/templates/fastapi-minimal`
|
|
287
|
+
|
|
288
|
+
It is intentionally boring. That is a feature. Teams need a truthful starting
|
|
289
|
+
point, not a framework demo that hides the protocol.
|
|
290
|
+
|
|
291
|
+
## Publishing Status
|
|
292
|
+
|
|
293
|
+
This package is released through the shared Python + Java SDK publishing process
|
|
294
|
+
documented in
|
|
295
|
+
[`docs/platform/remote-domain-sdk-publishing.md`](../docs/platform/remote-domain-sdk-publishing.md).
|
|
296
|
+
|
|
297
|
+
Release guardrail:
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
SDK_VERSION=0.1.2 scripts/check-remote-domain-sdk-release-gates.sh
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
PyPI artifacts are immutable. Do not rerun a publish for an already published
|
|
304
|
+
version; run smoke-only verification or bump the SDK version.
|
|
305
|
+
|
|
306
|
+
Public package note: PyPI distributions expose this SDK implementation. Keep
|
|
307
|
+
host runtime internals and domain-private logic out of this package.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "astraform-remote-domain-author-kit"
|
|
7
|
+
version = "0.1.2"
|
|
8
|
+
description = "Python author kit for Astraform remote-domain.v1 services"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
license = "Apache-2.0"
|
|
12
|
+
keywords = ["astraform", "remote-domain", "simulation", "conformance"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"Programming Language :: Python :: 3.12",
|
|
16
|
+
"Framework :: FastAPI",
|
|
17
|
+
"Typing :: Typed",
|
|
18
|
+
]
|
|
19
|
+
dependencies = []
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
Documentation = "https://github.com/astraform/platform/blob/main/docs/guides/remote-domain-authoring-guide.md"
|
|
23
|
+
Source = "https://github.com/astraform/platform"
|
|
24
|
+
Publishing = "https://github.com/astraform/platform/blob/main/docs/platform/remote-domain-sdk-publishing.md"
|
|
25
|
+
|
|
26
|
+
[project.optional-dependencies]
|
|
27
|
+
fastapi = [
|
|
28
|
+
"fastapi>=0.115,<1.0",
|
|
29
|
+
]
|
|
30
|
+
test = [
|
|
31
|
+
"fastapi>=0.115,<1.0",
|
|
32
|
+
"httpx>=0.28,<1.0",
|
|
33
|
+
"pytest>=8.3,<9.0",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[project.scripts]
|
|
37
|
+
astraform-remote-domain-copy-starter = "astraform.remote_domain.author_kit.templates:main"
|
|
38
|
+
|
|
39
|
+
[tool.setuptools]
|
|
40
|
+
package-dir = {"" = "src"}
|
|
41
|
+
include-package-data = true
|
|
42
|
+
|
|
43
|
+
[tool.setuptools.packages.find]
|
|
44
|
+
where = ["src"]
|
|
45
|
+
namespaces = true
|
|
46
|
+
|
|
47
|
+
[tool.setuptools.package-data]
|
|
48
|
+
"astraform.remote_domain.author_kit" = [
|
|
49
|
+
"templates/fastapi-minimal/README.md",
|
|
50
|
+
"templates/fastapi-minimal/pyproject.toml",
|
|
51
|
+
"templates/fastapi-minimal/scripts/*.sh",
|
|
52
|
+
"templates/fastapi-minimal/src/acme_remote_domain/*.json",
|
|
53
|
+
"templates/fastapi-minimal/src/acme_remote_domain/*.py",
|
|
54
|
+
"templates/fastapi-minimal/tests/*.py",
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
[tool.pytest.ini_options]
|
|
58
|
+
testpaths = ["tests"]
|