simjecture 0.1.0__py3-none-any.whl

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 (75) hide show
  1. conjecture_solver/__init__.py +10 -0
  2. conjecture_solver/__main__.py +4 -0
  3. conjecture_solver/action_handlers.py +459 -0
  4. conjecture_solver/adapters/__init__.py +7 -0
  5. conjecture_solver/adapters/base.py +96 -0
  6. conjecture_solver/adapters/fake.py +155 -0
  7. conjecture_solver/adapters/pic.py +196 -0
  8. conjecture_solver/adapters/warpx.py +1375 -0
  9. conjecture_solver/autonomous_research.py +1071 -0
  10. conjecture_solver/benchmarks/__init__.py +6 -0
  11. conjecture_solver/benchmarks/electrostatic_pic.py +547 -0
  12. conjecture_solver/benchmarks/kinetic_sufficiency.py +318 -0
  13. conjecture_solver/builtin_skills/warpx/SKILL.md +151 -0
  14. conjecture_solver/builtin_skills/warpx/agents/openai.yaml +4 -0
  15. conjecture_solver/builtin_skills/warpx/examples/implicit_em_smoke.py +74 -0
  16. conjecture_solver/builtin_skills/warpx/examples/minimal_smoke.py +54 -0
  17. conjecture_solver/builtin_skills/warpx/examples/openpmd_field_smoke.py +87 -0
  18. conjecture_solver/builtin_skills/warpx/manifest.json +11 -0
  19. conjecture_solver/builtin_skills/warpx/references/2d-xz-commissioning.md +145 -0
  20. conjecture_solver/builtin_skills/warpx/references/cpu-launch-tuning.md +54 -0
  21. conjecture_solver/builtin_skills/warpx/references/diagnostics.md +139 -0
  22. conjecture_solver/builtin_skills/warpx/references/gpu-launch-tuning.md +79 -0
  23. conjecture_solver/builtin_skills/warpx/references/local-cuda-deployment.md +87 -0
  24. conjecture_solver/builtin_skills/warpx/references/numerical-risks.md +32 -0
  25. conjecture_solver/builtin_skills/warpx/references/picmi-interface.md +118 -0
  26. conjecture_solver/builtin_skills/warpx/references/resource-scaling.md +24 -0
  27. conjecture_solver/builtin_skills/warpx/references/time-integration.md +58 -0
  28. conjecture_solver/builtin_skills/warpx/scripts/benchmark_cpu_threads.py +207 -0
  29. conjecture_solver/builtin_skills/warpx/scripts/benchmark_gpu.py +242 -0
  30. conjecture_solver/builtin_skills/warpx/scripts/bootstrap_local_cuda.sh +82 -0
  31. conjecture_solver/builtin_skills/warpx/scripts/probe_local_cuda.py +110 -0
  32. conjecture_solver/builtin_skills/warpx/scripts/reduced_energy_budget.py +195 -0
  33. conjecture_solver/builtin_skills/warpx/scripts/run_local_cuda.sh +39 -0
  34. conjecture_solver/campaign.py +406 -0
  35. conjecture_solver/cli.py +963 -0
  36. conjecture_solver/confirmation.py +287 -0
  37. conjecture_solver/control.py +234 -0
  38. conjecture_solver/deployment.py +874 -0
  39. conjecture_solver/discovery.py +68 -0
  40. conjecture_solver/domains/__init__.py +40 -0
  41. conjecture_solver/domains/base.py +157 -0
  42. conjecture_solver/domains/kinetic_sufficiency.py +521 -0
  43. conjecture_solver/ledger.py +238 -0
  44. conjecture_solver/lifecycle.py +131 -0
  45. conjecture_solver/literature.py +494 -0
  46. conjecture_solver/llm.py +228 -0
  47. conjecture_solver/models.py +279 -0
  48. conjecture_solver/mvp_agent.py +3212 -0
  49. conjecture_solver/mvp_claims.py +1279 -0
  50. conjecture_solver/mvp_control.py +322 -0
  51. conjecture_solver/mvp_guidance.py +172 -0
  52. conjecture_solver/mvp_launch.py +1296 -0
  53. conjecture_solver/mvp_monitor.py +1487 -0
  54. conjecture_solver/mvp_skills.py +538 -0
  55. conjecture_solver/orchestration.py +578 -0
  56. conjecture_solver/outbox.py +526 -0
  57. conjecture_solver/parameters.py +394 -0
  58. conjecture_solver/proposals.py +427 -0
  59. conjecture_solver/research_tools.py +278 -0
  60. conjecture_solver/schema_export.py +246 -0
  61. conjecture_solver/search.py +1047 -0
  62. conjecture_solver/semantics.py +146 -0
  63. conjecture_solver/tui/__init__.py +7 -0
  64. conjecture_solver/tui/app.py +202 -0
  65. conjecture_solver/tui/claim_views.py +213 -0
  66. conjecture_solver/tui/screens.py +1014 -0
  67. conjecture_solver/warpx_analysis.py +184 -0
  68. conjecture_solver/warpx_campaign.py +148 -0
  69. conjecture_solver/warpx_confirmation.py +617 -0
  70. simjecture-0.1.0.dist-info/METADATA +400 -0
  71. simjecture-0.1.0.dist-info/RECORD +75 -0
  72. simjecture-0.1.0.dist-info/WHEEL +4 -0
  73. simjecture-0.1.0.dist-info/entry_points.txt +4 -0
  74. simjecture-0.1.0.dist-info/licenses/LICENSE +202 -0
  75. simjecture-0.1.0.dist-info/licenses/NOTICE +6 -0
@@ -0,0 +1,278 @@
1
+ """Generic subprocess boundary for model-visible research capabilities."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import subprocess
8
+ import time
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+ from pydantic import Field
13
+
14
+ from .adapters.base import (
15
+ CostEstimate,
16
+ JobState,
17
+ SimulatorAdapter,
18
+ ValidationReport,
19
+ )
20
+ from .autonomous_research import (
21
+ ResearchToolManifest,
22
+ ResearchToolResult,
23
+ )
24
+ from .models import ExperimentSpec, StrictModel
25
+
26
+
27
+ class SubprocessResearchToolConfig(StrictModel):
28
+ manifest: ResearchToolManifest
29
+ command: tuple[str, ...] = Field(min_length=1)
30
+ working_directory: str | None = None
31
+ timeout_seconds: float = Field(gt=0)
32
+ estimated_cost: CostEstimate
33
+ environment: dict[str, str] = Field(default_factory=dict)
34
+
35
+ @classmethod
36
+ def read(cls, path: str | Path) -> SubprocessResearchToolConfig:
37
+ return cls.model_validate_json(Path(path).read_text())
38
+
39
+
40
+ def _json_type_matches(value: object, expected: str) -> bool:
41
+ if expected == "object":
42
+ return isinstance(value, dict)
43
+ if expected == "array":
44
+ return isinstance(value, list)
45
+ if expected == "string":
46
+ return isinstance(value, str)
47
+ if expected == "number":
48
+ return isinstance(value, (int, float)) and not isinstance(value, bool)
49
+ if expected == "integer":
50
+ return isinstance(value, int) and not isinstance(value, bool)
51
+ if expected == "boolean":
52
+ return isinstance(value, bool)
53
+ if expected == "null":
54
+ return value is None
55
+ return False
56
+
57
+
58
+ def _validate_schema(value: object, schema: dict[str, Any], path: str = "$") -> list[str]:
59
+ errors: list[str] = []
60
+ expected = schema.get("type")
61
+ if isinstance(expected, list):
62
+ if not any(_json_type_matches(value, str(item)) for item in expected):
63
+ return [f"{path} does not match any allowed JSON type"]
64
+ elif isinstance(expected, str) and not _json_type_matches(value, expected):
65
+ return [f"{path} must have JSON type {expected}"]
66
+ if "enum" in schema and value not in schema["enum"]:
67
+ errors.append(f"{path} is not in the allowed enum")
68
+ if isinstance(value, dict):
69
+ properties = schema.get("properties", {})
70
+ required = schema.get("required", [])
71
+ for name in required:
72
+ if name not in value:
73
+ errors.append(f"{path}.{name} is required")
74
+ if schema.get("additionalProperties") is False:
75
+ extras = set(value) - set(properties)
76
+ if extras:
77
+ errors.append(f"{path} has unsupported properties: {sorted(extras)}")
78
+ for name, item in value.items():
79
+ child_schema = properties.get(name)
80
+ if isinstance(child_schema, dict):
81
+ errors.extend(_validate_schema(item, child_schema, f"{path}.{name}"))
82
+ elif isinstance(value, list) and isinstance(schema.get("items"), dict):
83
+ for index, item in enumerate(value):
84
+ errors.extend(_validate_schema(item, schema["items"], f"{path}[{index}]"))
85
+ elif isinstance(value, (int, float)) and not isinstance(value, bool):
86
+ if "minimum" in schema and value < schema["minimum"]:
87
+ errors.append(f"{path} is below its minimum")
88
+ if "maximum" in schema and value > schema["maximum"]:
89
+ errors.append(f"{path} exceeds its maximum")
90
+ return errors
91
+
92
+
93
+ class SubprocessResearchTool:
94
+ """Run an explicitly installed JSON-in/JSON-out tool without a shell."""
95
+
96
+ def __init__(self, config: SubprocessResearchToolConfig) -> None:
97
+ self.config = config
98
+ self.manifest = config.manifest
99
+
100
+ def validate(self, arguments: dict[str, Any]) -> ValidationReport:
101
+ errors = tuple(_validate_schema(arguments, self.manifest.input_schema))
102
+ return ValidationReport(valid=not errors, errors=errors)
103
+
104
+ def estimate_cost(self, arguments: dict[str, Any]) -> CostEstimate:
105
+ del arguments
106
+ return self.config.estimated_cost
107
+
108
+ def execute(
109
+ self,
110
+ arguments: dict[str, Any],
111
+ *,
112
+ idempotency_key: str,
113
+ ) -> ResearchToolResult:
114
+ validation = self.validate(arguments)
115
+ if not validation.valid:
116
+ raise ValueError("invalid subprocess tool arguments: " + "; ".join(validation.errors))
117
+ envelope = {
118
+ "schema_version": "0.1.0",
119
+ "tool": self.manifest.name,
120
+ "tool_version": self.manifest.version,
121
+ "idempotency_key": idempotency_key,
122
+ "arguments": arguments,
123
+ }
124
+ environment = os.environ.copy()
125
+ environment.update(self.config.environment)
126
+ started = time.monotonic()
127
+ completed = subprocess.run(
128
+ list(self.config.command),
129
+ input=json.dumps(envelope, sort_keys=True),
130
+ capture_output=True,
131
+ text=True,
132
+ cwd=self.config.working_directory,
133
+ env=environment,
134
+ timeout=self.config.timeout_seconds,
135
+ check=False,
136
+ )
137
+ elapsed = time.monotonic() - started
138
+ if completed.returncode != 0:
139
+ detail = completed.stderr.strip() or f"exit status {completed.returncode}"
140
+ return ResearchToolResult(
141
+ completed=False,
142
+ validity_checks={"subprocess_exit_zero": False},
143
+ diagnostics={"returncode": completed.returncode},
144
+ cost=self.config.estimated_cost.model_copy(
145
+ update={"wall_seconds": elapsed}
146
+ ),
147
+ scientific_scope=f"failed execution of {self.manifest.name}",
148
+ failure_detail=detail[:2000],
149
+ )
150
+ try:
151
+ result = ResearchToolResult.model_validate_json(completed.stdout)
152
+ except ValueError as error:
153
+ raise ValueError(
154
+ f"research tool {self.manifest.name} returned invalid JSON: {error}"
155
+ ) from error
156
+ output_errors = _validate_schema(result.observables, self.manifest.output_schema)
157
+ if output_errors:
158
+ raise ValueError(
159
+ "research tool output violated its manifest: " + "; ".join(output_errors)
160
+ )
161
+ return result
162
+
163
+
164
+ class SimulatorAdapterResearchTool:
165
+ """Expose any simulator adapter as a model-visible generic research tool."""
166
+
167
+ def __init__(
168
+ self,
169
+ adapter: SimulatorAdapter,
170
+ *,
171
+ name: str | None = None,
172
+ description: str | None = None,
173
+ supported_observables: tuple[str, ...] | None = None,
174
+ poll_interval_seconds: float = 0.1,
175
+ monitor_timeout_seconds: float = 3600.0,
176
+ ) -> None:
177
+ capabilities = adapter.capabilities()
178
+ self.adapter = adapter
179
+ self.poll_interval_seconds = poll_interval_seconds
180
+ self.monitor_timeout_seconds = monitor_timeout_seconds
181
+ observables = supported_observables or capabilities.supported_observable_kinds
182
+ self.manifest = ResearchToolManifest(
183
+ name=name or capabilities.adapter_name.replace("_", "-"),
184
+ version=capabilities.adapter_version,
185
+ description=description or f"Simulator adapter {capabilities.adapter_name}",
186
+ kind="simulator",
187
+ input_schema={
188
+ "type": "object",
189
+ "properties": {"experiment": ExperimentSpec.model_json_schema()},
190
+ "required": ["experiment"],
191
+ "additionalProperties": False,
192
+ },
193
+ output_schema={
194
+ "type": "object",
195
+ "properties": {name: {} for name in observables},
196
+ },
197
+ supported_model_families=capabilities.supported_models,
198
+ supported_coordinates=capabilities.supported_coordinates,
199
+ supported_observables=observables,
200
+ )
201
+
202
+ def _experiment(self, arguments: dict[str, Any]) -> ExperimentSpec:
203
+ try:
204
+ return ExperimentSpec.model_validate(arguments["experiment"])
205
+ except (KeyError, TypeError, ValueError) as error:
206
+ raise ValueError(f"invalid simulator experiment: {error}") from error
207
+
208
+ def validate(self, arguments: dict[str, Any]) -> ValidationReport:
209
+ try:
210
+ experiment = self._experiment(arguments)
211
+ except ValueError as error:
212
+ return ValidationReport(valid=False, errors=(str(error),))
213
+ return self.adapter.validate(experiment)
214
+
215
+ def estimate_cost(self, arguments: dict[str, Any]) -> CostEstimate:
216
+ experiment = self._experiment(arguments)
217
+ report = self.adapter.validate(experiment)
218
+ if not report.valid:
219
+ raise ValueError("invalid simulator experiment: " + "; ".join(report.errors))
220
+ return self.adapter.estimate_cost(experiment)
221
+
222
+ def execute(
223
+ self,
224
+ arguments: dict[str, Any],
225
+ *,
226
+ idempotency_key: str,
227
+ ) -> ResearchToolResult:
228
+ experiment = self._experiment(arguments)
229
+ validation = self.adapter.validate(experiment)
230
+ if not validation.valid:
231
+ raise ValueError("invalid simulator experiment: " + "; ".join(validation.errors))
232
+ estimate = self.adapter.estimate_cost(experiment)
233
+ run = self.adapter.compile_input(experiment)
234
+ job = self.adapter.submit(run, idempotency_key=idempotency_key)
235
+ deadline = time.monotonic() + self.monitor_timeout_seconds
236
+ while True:
237
+ status = self.adapter.monitor(job)
238
+ if status.state is JobState.COMPLETED:
239
+ break
240
+ if status.state in {JobState.FAILED, JobState.CANCELLED, JobState.UNKNOWN}:
241
+ return ResearchToolResult(
242
+ completed=False,
243
+ diagnostics={"job_state": status.state.value, "detail": status.detail},
244
+ validity_checks={"job_completed": False},
245
+ cost=estimate,
246
+ scientific_scope=f"failed {self.manifest.name} execution",
247
+ failure_detail=status.detail or f"job ended in {status.state.value}",
248
+ )
249
+ if time.monotonic() >= deadline:
250
+ return ResearchToolResult(
251
+ completed=False,
252
+ diagnostics={"job_state": status.state.value},
253
+ validity_checks={"job_completed_before_timeout": False},
254
+ cost=estimate,
255
+ scientific_scope=f"timed-out {self.manifest.name} execution",
256
+ failure_detail="simulator monitor timeout",
257
+ )
258
+ time.sleep(self.poll_interval_seconds)
259
+ normalized = self.adapter.normalize(self.adapter.retrieve(job))
260
+ checks = {"adapter_admission": True, "job_completed": True}
261
+ gate = normalized.observables.get("instrument_gates_passed")
262
+ if isinstance(gate, bool):
263
+ checks["instrument_gates_passed"] = gate
264
+ evidence = normalized.observables.get("scientific_evidence_eligible")
265
+ if isinstance(evidence, bool):
266
+ checks["scientific_evidence_eligible"] = evidence
267
+ return ResearchToolResult(
268
+ completed=True,
269
+ observables=normalized.observables,
270
+ diagnostics=normalized.diagnostics,
271
+ validity_checks=checks,
272
+ artifact_hashes=normalized.artifact_hashes,
273
+ cost=estimate,
274
+ scientific_scope=(
275
+ f"{self.manifest.name} in model families "
276
+ + ", ".join(self.manifest.supported_model_families)
277
+ ),
278
+ )
@@ -0,0 +1,246 @@
1
+ """Generate stable public JSON Schemas for typed boundary objects."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+ from typing import TypeAlias
8
+
9
+ from pydantic import BaseModel
10
+
11
+ from .adapters.base import CapabilityManifest, NormalizedResult
12
+ from .adapters.warpx import (
13
+ WarpXCalibrationPoint,
14
+ WarpXCaseSummary,
15
+ WarpXCompiledCase,
16
+ WarpXExecutionProfile,
17
+ WarpXNumericalConfig,
18
+ WarpXPairSummary,
19
+ WarpXPhysicalConfig,
20
+ WarpXPhysicsQualificationRecord,
21
+ WarpXQualificationRecord,
22
+ WarpXQualifiedScope,
23
+ )
24
+ from .autonomous_research import (
25
+ CandidateResult,
26
+ ObservablePrediction,
27
+ ProposedToolCall,
28
+ ResearchBudget,
29
+ ResearchCampaignReport,
30
+ ResearchConclusion,
31
+ ResearchDecision,
32
+ ResearchEvidencePolicy,
33
+ ResearchHypothesis,
34
+ ResearchObservation,
35
+ ResearchProblemContract,
36
+ ResearchRelation,
37
+ ResearchToolManifest,
38
+ ResearchToolResult,
39
+ )
40
+ from .benchmarks.electrostatic_pic import (
41
+ PICCaseResult,
42
+ PICConfig,
43
+ PICMixtureCaseResult,
44
+ PICNumericalConfig,
45
+ PICSufficiencyResult,
46
+ )
47
+ from .benchmarks.kinetic_sufficiency import KineticSufficiencyResult
48
+ from .confirmation import (
49
+ PICConfirmationAttempt,
50
+ PICConfirmationDesign,
51
+ PICConfirmationReport,
52
+ )
53
+ from .control import ControlDirective
54
+ from .discovery import DiscoveryPackage
55
+ from .domains.base import (
56
+ DomainDiagnosisSummary,
57
+ DomainEvolutionSummary,
58
+ DomainPluginMetadata,
59
+ DomainRunSummary,
60
+ DomainTemplateSummary,
61
+ )
62
+ from .domains.kinetic_sufficiency import (
63
+ KineticSufficiencyDiscoveryPackage,
64
+ KineticSufficiencyHypothesisInput,
65
+ KineticSufficiencySolveResult,
66
+ )
67
+ from .lifecycle import CampaignState, LifecycleEvent
68
+ from .models import (
69
+ AttemptRecord,
70
+ CampaignCheckpoint,
71
+ Claim,
72
+ DecisionRecord,
73
+ ExperimentSpec,
74
+ HumanIntervention,
75
+ HypothesisNode,
76
+ MatchedPairFormalPredicate,
77
+ RunEvidence,
78
+ )
79
+ from .mvp_guidance import MVPGuidedCommissioningSpec
80
+ from .orchestration import (
81
+ ActionExecution,
82
+ ActionFailureRecord,
83
+ CampaignAction,
84
+ CampaignActionGraph,
85
+ CampaignActionState,
86
+ CampaignBudget,
87
+ MultiActionCampaignReport,
88
+ )
89
+ from .outbox import DispatchAttempt, ExternalReceipt, OutboxIntent
90
+ from .parameters import (
91
+ NumericalAssessment,
92
+ NumericalDiagnostics,
93
+ NumericalGate,
94
+ ParameterSpace,
95
+ ReferenceQualification,
96
+ RunPlan,
97
+ )
98
+ from .proposals import (
99
+ ModelCallProvenance,
100
+ ProposalDraft,
101
+ ProposalRecord,
102
+ ProposalRequest,
103
+ ProposalValidation,
104
+ )
105
+ from .research_tools import SubprocessResearchToolConfig
106
+ from .search import (
107
+ AIStrategyDraft,
108
+ BlindedSearchReport,
109
+ BlindedSearchRequest,
110
+ CandidateEvaluation,
111
+ SearchStrategy,
112
+ SymmetricMixtureCandidate,
113
+ )
114
+ from .warpx_campaign import QualifiedWarpXCampaignPackage
115
+ from .warpx_confirmation import (
116
+ QualifiedWarpXInstrument,
117
+ WarpXConfirmationAttempt,
118
+ WarpXConfirmationDesign,
119
+ WarpXConfirmationFailure,
120
+ WarpXConfirmationReport,
121
+ WarpXConfirmationResolution,
122
+ )
123
+
124
+ ModelType: TypeAlias = type[BaseModel]
125
+
126
+ PUBLIC_MODELS: tuple[ModelType, ...] = (
127
+ ResearchHypothesis,
128
+ ResearchRelation,
129
+ ResearchBudget,
130
+ ResearchEvidencePolicy,
131
+ ResearchProblemContract,
132
+ ResearchToolManifest,
133
+ ObservablePrediction,
134
+ ProposedToolCall,
135
+ CandidateResult,
136
+ ResearchConclusion,
137
+ ResearchDecision,
138
+ ResearchToolResult,
139
+ ResearchObservation,
140
+ ResearchCampaignReport,
141
+ SubprocessResearchToolConfig,
142
+ MVPGuidedCommissioningSpec,
143
+ HypothesisNode,
144
+ MatchedPairFormalPredicate,
145
+ ExperimentSpec,
146
+ AttemptRecord,
147
+ RunEvidence,
148
+ Claim,
149
+ DecisionRecord,
150
+ CampaignCheckpoint,
151
+ HumanIntervention,
152
+ CampaignState,
153
+ LifecycleEvent,
154
+ CapabilityManifest,
155
+ NormalizedResult,
156
+ KineticSufficiencyResult,
157
+ DiscoveryPackage,
158
+ DomainPluginMetadata,
159
+ DomainRunSummary,
160
+ DomainTemplateSummary,
161
+ DomainEvolutionSummary,
162
+ DomainDiagnosisSummary,
163
+ KineticSufficiencyHypothesisInput,
164
+ KineticSufficiencySolveResult,
165
+ KineticSufficiencyDiscoveryPackage,
166
+ ProposalRequest,
167
+ ProposalDraft,
168
+ ProposalValidation,
169
+ ModelCallProvenance,
170
+ ProposalRecord,
171
+ ParameterSpace,
172
+ ReferenceQualification,
173
+ RunPlan,
174
+ NumericalDiagnostics,
175
+ NumericalGate,
176
+ NumericalAssessment,
177
+ ControlDirective,
178
+ PICConfig,
179
+ PICNumericalConfig,
180
+ PICCaseResult,
181
+ PICMixtureCaseResult,
182
+ PICSufficiencyResult,
183
+ SymmetricMixtureCandidate,
184
+ BlindedSearchRequest,
185
+ AIStrategyDraft,
186
+ SearchStrategy,
187
+ CandidateEvaluation,
188
+ BlindedSearchReport,
189
+ PICConfirmationDesign,
190
+ PICConfirmationAttempt,
191
+ PICConfirmationReport,
192
+ CampaignAction,
193
+ CampaignBudget,
194
+ CampaignActionGraph,
195
+ ActionExecution,
196
+ ActionFailureRecord,
197
+ CampaignActionState,
198
+ MultiActionCampaignReport,
199
+ OutboxIntent,
200
+ DispatchAttempt,
201
+ ExternalReceipt,
202
+ WarpXExecutionProfile,
203
+ WarpXPhysicalConfig,
204
+ WarpXNumericalConfig,
205
+ WarpXCompiledCase,
206
+ WarpXCaseSummary,
207
+ WarpXPairSummary,
208
+ WarpXQualificationRecord,
209
+ WarpXQualifiedScope,
210
+ WarpXCalibrationPoint,
211
+ WarpXPhysicsQualificationRecord,
212
+ QualifiedWarpXInstrument,
213
+ WarpXConfirmationResolution,
214
+ WarpXConfirmationDesign,
215
+ WarpXConfirmationAttempt,
216
+ WarpXConfirmationFailure,
217
+ WarpXConfirmationReport,
218
+ QualifiedWarpXCampaignPackage,
219
+ )
220
+
221
+
222
+ def rendered_schemas() -> dict[str, str]:
223
+ return {
224
+ model.__name__: json.dumps(
225
+ model.model_json_schema(),
226
+ indent=2,
227
+ sort_keys=True,
228
+ )
229
+ + "\n"
230
+ for model in PUBLIC_MODELS
231
+ }
232
+
233
+
234
+ def export_schemas(output: Path, *, check: bool = False) -> list[str]:
235
+ rendered = rendered_schemas()
236
+ mismatches: list[str] = []
237
+ if not check:
238
+ output.mkdir(parents=True, exist_ok=True)
239
+ for name, content in rendered.items():
240
+ path = output / f"{name}.schema.json"
241
+ if check:
242
+ if not path.exists() or path.read_text() != content:
243
+ mismatches.append(str(path))
244
+ else:
245
+ path.write_text(content)
246
+ return mismatches