flowspec2 1.0.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.
- flowspec2/__init__.py +105 -0
- flowspec2/authoring/__init__.py +275 -0
- flowspec2/authoring/authoring-evidence-signature.schema.json +34 -0
- flowspec2/authoring/authoring-evidence.schema.json +455 -0
- flowspec2/authoring/authoring-operational-evidence.schema.json +160 -0
- flowspec2/authoring/benchmark.py +1409 -0
- flowspec2/authoring/corpus/await_correction.case.json +220 -0
- flowspec2/authoring/corpus/flowspec2.ctk.json +769 -0
- flowspec2/authoring/corpus/gated_derive.case.json +100 -0
- flowspec2/authoring/corpus/linear.case.json +55 -0
- flowspec2/authoring/corpus/manifest.json +31 -0
- flowspec2/authoring/corpus/subflow.case.json +66 -0
- flowspec2/authoring/corpus/terminal.case.json +94 -0
- flowspec2/authoring/corpus.py +307 -0
- flowspec2/authoring/ctk.py +836 -0
- flowspec2/authoring/detached_signature.py +120 -0
- flowspec2/authoring/evidence.py +311 -0
- flowspec2/authoring/evidence_signature.py +187 -0
- flowspec2/authoring/evidence_verification.py +229 -0
- flowspec2/authoring/gemini.py +215 -0
- flowspec2/authoring/operational-corpus.json +61 -0
- flowspec2/authoring/operational.py +956 -0
- flowspec2/authoring/operational_providers.py +167 -0
- flowspec2/authoring/presentation_review.py +1013 -0
- flowspec2/authoring/presentation_review_signature.py +305 -0
- flowspec2/authoring/projection.py +299 -0
- flowspec2/authoring/provider_prompt.py +83 -0
- flowspec2/backends/__init__.py +82 -0
- flowspec2/backends/http.py +189 -0
- flowspec2/checker.py +238 -0
- flowspec2/cli.py +789 -0
- flowspec2/cli_parser.py +345 -0
- flowspec2/clock.py +23 -0
- flowspec2/compat/__init__.py +47 -0
- flowspec2/compat/models.py +82 -0
- flowspec2/compat/open_workflow.py +616 -0
- flowspec2/compat/rasa.py +19 -0
- flowspec2/compat/rasa_export.py +876 -0
- flowspec2/compat/rasa_import.py +992 -0
- flowspec2/compat/rasa_shared.py +270 -0
- flowspec2/compat/schemas/open-workflow-conversation-1.schema.json +138 -0
- flowspec2/compat/schemas/vendor/open-workflow-1.0.3.LICENSE +201 -0
- flowspec2/compat/schemas/vendor/open-workflow-1.0.3.provenance.json +13 -0
- flowspec2/compat/schemas/vendor/open-workflow-1.0.3.workflow.yaml +1956 -0
- flowspec2/compat/tool_profiles.py +149 -0
- flowspec2/compat/yaml.py +147 -0
- flowspec2/compiler.py +957 -0
- flowspec2/compiler_contracts.py +17 -0
- flowspec2/compiler_resume_contracts.py +594 -0
- flowspec2/compiler_schema_relations.py +1830 -0
- flowspec2/compiler_tool_contracts.py +245 -0
- flowspec2/compiler_value_contracts.py +447 -0
- flowspec2/derive.py +32 -0
- flowspec2/diagnostics.py +94 -0
- flowspec2/domains.py +489 -0
- flowspec2/experimental/__init__.py +57 -0
- flowspec2/experimental/flowspec-3-draft.schema.json +923 -0
- flowspec2/experimental/v3-preview-loss-policy.json +161 -0
- flowspec2/experimental/v3_lowering.py +928 -0
- flowspec2/experimental/v3_preview.py +2460 -0
- flowspec2/flowspec-2.schema.json +635 -0
- flowspec2/interactive.py +300 -0
- flowspec2/ir.py +1459 -0
- flowspec2/json_codec.py +120 -0
- flowspec2/llm.py +366 -0
- flowspec2/models.py +121 -0
- flowspec2/nodes.py +1537 -0
- flowspec2/observability.py +151 -0
- flowspec2/predicates.py +89 -0
- flowspec2/profiles.py +118 -0
- flowspec2/py.typed +0 -0
- flowspec2/runtime.py +1059 -0
- flowspec2/schema.py +54 -0
- flowspec2/schema_contracts.py +203 -0
- flowspec2/semantic_derive_contracts.py +530 -0
- flowspec2/semantic_path_contracts.py +528 -0
- flowspec2/semantic_predicate_contracts.py +355 -0
- flowspec2/semantic_schema_contracts.py +137 -0
- flowspec2/semantic_source_contracts.py +21 -0
- flowspec2/semantic_state_contracts.py +450 -0
- flowspec2/semantic_support.py +40 -0
- flowspec2/semantics.py +410 -0
- flowspec2/state_migration.py +1034 -0
- flowspec2/subflows/__init__.py +1117 -0
- flowspec2/subflows/address.py +328 -0
- flowspec2/subflows/identification.py +1031 -0
- flowspec2/tools.py +1154 -0
- flowspec2-1.0.0.dist-info/METADATA +482 -0
- flowspec2-1.0.0.dist-info/RECORD +92 -0
- flowspec2-1.0.0.dist-info/WHEEL +4 -0
- flowspec2-1.0.0.dist-info/entry_points.txt +2 -0
- flowspec2-1.0.0.dist-info/licenses/LICENSE +21 -0
flowspec2/cli_parser.py
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
"""Argument grammar for the flowspec2 command-line interface."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import logging
|
|
7
|
+
import sys
|
|
8
|
+
from collections.abc import Callable
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import Never
|
|
11
|
+
|
|
12
|
+
from .observability import log_event
|
|
13
|
+
|
|
14
|
+
CommandHandler = Callable[[argparse.Namespace], int]
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CorrelatedArgumentParser(argparse.ArgumentParser):
|
|
19
|
+
"""Argument parser whose user-facing failures carry a correlation ID."""
|
|
20
|
+
|
|
21
|
+
def error(self, message: str) -> Never:
|
|
22
|
+
log_id = log_event(
|
|
23
|
+
logger,
|
|
24
|
+
logging.ERROR,
|
|
25
|
+
"CLI argument parsing failed",
|
|
26
|
+
operation="parse_arguments",
|
|
27
|
+
context={"parser": self.prog},
|
|
28
|
+
)
|
|
29
|
+
self.print_usage(sys.stderr)
|
|
30
|
+
self.exit(2, f"{self.prog}: error [log_id={log_id}]: {message}\n")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True)
|
|
34
|
+
class CliHandlers:
|
|
35
|
+
"""Handlers and defaults injected into the declarative argument grammar."""
|
|
36
|
+
|
|
37
|
+
validate: CommandHandler
|
|
38
|
+
check: CommandHandler
|
|
39
|
+
normalize: CommandHandler
|
|
40
|
+
intermediate_representation: CommandHandler
|
|
41
|
+
graph: CommandHandler
|
|
42
|
+
mermaid: CommandHandler
|
|
43
|
+
rasa_export: CommandHandler
|
|
44
|
+
rasa_import: CommandHandler
|
|
45
|
+
open_workflow_export: CommandHandler
|
|
46
|
+
open_workflow_import: CommandHandler
|
|
47
|
+
authoring_benchmark_gemini: CommandHandler
|
|
48
|
+
operational_benchmark_gemini: CommandHandler
|
|
49
|
+
operational_evidence_verify: CommandHandler
|
|
50
|
+
authoring_evidence_verify: CommandHandler
|
|
51
|
+
authoring_evidence_sign: CommandHandler
|
|
52
|
+
authoring_evidence_signature_verify: CommandHandler
|
|
53
|
+
authoring_presentation_review_init: CommandHandler
|
|
54
|
+
authoring_presentation_review_finalize: CommandHandler
|
|
55
|
+
authoring_presentation_review_verify: CommandHandler
|
|
56
|
+
authoring_presentation_review_sign: CommandHandler
|
|
57
|
+
authoring_presentation_review_signature_verify: CommandHandler
|
|
58
|
+
authoring_promotion_verify: CommandHandler
|
|
59
|
+
default_gemini_model: str
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def build_parser(cli_handlers: CliHandlers) -> argparse.ArgumentParser:
|
|
63
|
+
"""Build a fresh parser bound to the supplied command handlers."""
|
|
64
|
+
|
|
65
|
+
parser = CorrelatedArgumentParser(
|
|
66
|
+
prog="flowspec2",
|
|
67
|
+
description="Validate, inspect, and convert flowspec2 documents.",
|
|
68
|
+
)
|
|
69
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
70
|
+
|
|
71
|
+
validate_parser = subparsers.add_parser(
|
|
72
|
+
"validate",
|
|
73
|
+
help="validate a flowspec2 document",
|
|
74
|
+
)
|
|
75
|
+
validate_parser.add_argument("path")
|
|
76
|
+
validate_parser.set_defaults(handler=cli_handlers.validate)
|
|
77
|
+
|
|
78
|
+
check_parser = subparsers.add_parser(
|
|
79
|
+
"check",
|
|
80
|
+
help="validate and compile-check a flowspec2 document",
|
|
81
|
+
)
|
|
82
|
+
check_parser.add_argument("path")
|
|
83
|
+
check_parser.add_argument(
|
|
84
|
+
"--json",
|
|
85
|
+
action="store_true",
|
|
86
|
+
dest="json_output",
|
|
87
|
+
help="emit a deterministic machine-readable report",
|
|
88
|
+
)
|
|
89
|
+
check_parser.set_defaults(handler=cli_handlers.check)
|
|
90
|
+
|
|
91
|
+
normalize_parser = subparsers.add_parser(
|
|
92
|
+
"normalize",
|
|
93
|
+
help="emit the canonical normalized flowspec2 document",
|
|
94
|
+
)
|
|
95
|
+
normalize_parser.add_argument("path")
|
|
96
|
+
normalize_parser.set_defaults(handler=cli_handlers.normalize)
|
|
97
|
+
|
|
98
|
+
intermediate_representation_parser = subparsers.add_parser(
|
|
99
|
+
"ir",
|
|
100
|
+
help="emit canonical contracts with subflow topology abstracted",
|
|
101
|
+
)
|
|
102
|
+
intermediate_representation_parser.add_argument("path")
|
|
103
|
+
intermediate_representation_parser.set_defaults(
|
|
104
|
+
handler=cli_handlers.intermediate_representation
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
graph_parser = subparsers.add_parser("graph", help="list compiled graph nodes")
|
|
108
|
+
graph_parser.add_argument("path")
|
|
109
|
+
graph_parser.set_defaults(handler=cli_handlers.graph)
|
|
110
|
+
|
|
111
|
+
mermaid_parser = subparsers.add_parser(
|
|
112
|
+
"mermaid",
|
|
113
|
+
help="render the fully expanded compiled graph as Mermaid",
|
|
114
|
+
)
|
|
115
|
+
mermaid_parser.add_argument("path")
|
|
116
|
+
mermaid_parser.set_defaults(handler=cli_handlers.mermaid)
|
|
117
|
+
|
|
118
|
+
rasa_export_parser = subparsers.add_parser(
|
|
119
|
+
"rasa-export",
|
|
120
|
+
help="export the bounded Rasa CALM portable profile",
|
|
121
|
+
)
|
|
122
|
+
rasa_export_parser.add_argument("path")
|
|
123
|
+
rasa_export_parser.add_argument("--output-dir", required=True)
|
|
124
|
+
rasa_export_parser.add_argument("--allow-lossy", action="store_true")
|
|
125
|
+
rasa_export_parser.set_defaults(handler=cli_handlers.rasa_export)
|
|
126
|
+
|
|
127
|
+
rasa_import_parser = subparsers.add_parser(
|
|
128
|
+
"rasa-import",
|
|
129
|
+
help="import one flow from the bounded Rasa CALM portable profile",
|
|
130
|
+
)
|
|
131
|
+
rasa_import_parser.add_argument("path", help="flows.yml path")
|
|
132
|
+
rasa_import_parser.add_argument("--domain", required=True, help="domain.yml path")
|
|
133
|
+
rasa_import_parser.add_argument("--flow", help="flow id; required for multi-flow files")
|
|
134
|
+
rasa_import_parser.add_argument("--version", default="1.0.0", help="flowspec2 version")
|
|
135
|
+
rasa_import_parser.add_argument("--output", required=True)
|
|
136
|
+
rasa_import_parser.add_argument("--allow-lossy", action="store_true")
|
|
137
|
+
rasa_import_parser.set_defaults(handler=cli_handlers.rasa_import)
|
|
138
|
+
|
|
139
|
+
open_export_parser = subparsers.add_parser(
|
|
140
|
+
"open-workflow-export",
|
|
141
|
+
help="export the lossless Open Workflow conversational profile",
|
|
142
|
+
)
|
|
143
|
+
open_export_parser.add_argument("path")
|
|
144
|
+
open_export_parser.add_argument("--output", required=True)
|
|
145
|
+
open_export_parser.add_argument("--namespace", default="flowspec2")
|
|
146
|
+
open_export_parser.set_defaults(handler=cli_handlers.open_workflow_export)
|
|
147
|
+
|
|
148
|
+
open_import_parser = subparsers.add_parser(
|
|
149
|
+
"open-workflow-import",
|
|
150
|
+
help="import the exact Open Workflow conversational profile",
|
|
151
|
+
)
|
|
152
|
+
open_import_parser.add_argument("path")
|
|
153
|
+
open_import_parser.add_argument("--output", required=True)
|
|
154
|
+
open_import_parser.set_defaults(handler=cli_handlers.open_workflow_import)
|
|
155
|
+
|
|
156
|
+
authoring_benchmark_parser = subparsers.add_parser(
|
|
157
|
+
"authoring-benchmark-gemini",
|
|
158
|
+
help="run the packaged AI-authoring corpus through Gemini",
|
|
159
|
+
)
|
|
160
|
+
authoring_benchmark_parser.add_argument(
|
|
161
|
+
"--allow-network",
|
|
162
|
+
action="store_true",
|
|
163
|
+
required=True,
|
|
164
|
+
help="explicitly permit Gemini API requests for this invocation",
|
|
165
|
+
)
|
|
166
|
+
authoring_benchmark_parser.add_argument(
|
|
167
|
+
"--benchmark-identifier",
|
|
168
|
+
default="gemini_reference",
|
|
169
|
+
)
|
|
170
|
+
authoring_benchmark_parser.add_argument(
|
|
171
|
+
"--model",
|
|
172
|
+
default=cli_handlers.default_gemini_model,
|
|
173
|
+
)
|
|
174
|
+
authoring_benchmark_parser.add_argument("--repository-revision", required=True)
|
|
175
|
+
authoring_benchmark_parser.add_argument("--max-correction-rounds", type=int, default=2)
|
|
176
|
+
authoring_benchmark_parser.add_argument("--output", required=True)
|
|
177
|
+
authoring_benchmark_parser.set_defaults(handler=cli_handlers.authoring_benchmark_gemini)
|
|
178
|
+
|
|
179
|
+
operational_gemini_parser = subparsers.add_parser(
|
|
180
|
+
"operational-benchmark-gemini",
|
|
181
|
+
help="probe final authored flows through Gemini routing and extraction",
|
|
182
|
+
)
|
|
183
|
+
operational_gemini_parser.add_argument("path")
|
|
184
|
+
operational_gemini_parser.add_argument(
|
|
185
|
+
"--allow-network",
|
|
186
|
+
action="store_true",
|
|
187
|
+
required=True,
|
|
188
|
+
help="explicitly permit Gemini API requests for this invocation",
|
|
189
|
+
)
|
|
190
|
+
operational_gemini_parser.add_argument(
|
|
191
|
+
"--model",
|
|
192
|
+
default=cli_handlers.default_gemini_model,
|
|
193
|
+
)
|
|
194
|
+
operational_gemini_parser.add_argument("--repository-revision", required=True)
|
|
195
|
+
operational_gemini_parser.add_argument("--output", required=True)
|
|
196
|
+
operational_gemini_parser.set_defaults(handler=cli_handlers.operational_benchmark_gemini)
|
|
197
|
+
|
|
198
|
+
operational_verify_parser = subparsers.add_parser(
|
|
199
|
+
"operational-evidence-verify",
|
|
200
|
+
help="verify and replay report-only operational model evidence",
|
|
201
|
+
)
|
|
202
|
+
operational_verify_parser.add_argument("path")
|
|
203
|
+
operational_verify_parser.add_argument("--authoring-evidence", required=True)
|
|
204
|
+
operational_verify_parser.add_argument("--repository-revision")
|
|
205
|
+
operational_verify_parser.add_argument(
|
|
206
|
+
"--json",
|
|
207
|
+
action="store_true",
|
|
208
|
+
dest="json_output",
|
|
209
|
+
help="emit a deterministic machine-readable verification summary",
|
|
210
|
+
)
|
|
211
|
+
operational_verify_parser.set_defaults(handler=cli_handlers.operational_evidence_verify)
|
|
212
|
+
|
|
213
|
+
evidence_verify_parser = subparsers.add_parser(
|
|
214
|
+
"authoring-evidence-verify",
|
|
215
|
+
help="verify and deterministically replay an authoring evidence artifact",
|
|
216
|
+
)
|
|
217
|
+
evidence_verify_parser.add_argument("path")
|
|
218
|
+
evidence_verify_parser.add_argument(
|
|
219
|
+
"--repository-revision",
|
|
220
|
+
help="require the artifact to name this repository revision",
|
|
221
|
+
)
|
|
222
|
+
evidence_verify_parser.add_argument(
|
|
223
|
+
"--json",
|
|
224
|
+
action="store_true",
|
|
225
|
+
dest="json_output",
|
|
226
|
+
help="emit a deterministic machine-readable verification summary",
|
|
227
|
+
)
|
|
228
|
+
evidence_verify_parser.set_defaults(handler=cli_handlers.authoring_evidence_verify)
|
|
229
|
+
|
|
230
|
+
evidence_sign_parser = subparsers.add_parser(
|
|
231
|
+
"authoring-evidence-sign",
|
|
232
|
+
help="sign a verified authoring evidence digest with Ed25519",
|
|
233
|
+
)
|
|
234
|
+
evidence_sign_parser.add_argument("path")
|
|
235
|
+
evidence_sign_parser.add_argument("--private-key", required=True)
|
|
236
|
+
evidence_sign_parser.add_argument("--repository-revision")
|
|
237
|
+
evidence_sign_parser.add_argument("--output", required=True)
|
|
238
|
+
evidence_sign_parser.set_defaults(handler=cli_handlers.authoring_evidence_sign)
|
|
239
|
+
|
|
240
|
+
signature_verify_parser = subparsers.add_parser(
|
|
241
|
+
"authoring-evidence-signature-verify",
|
|
242
|
+
help="authenticate authoring evidence with a trusted Ed25519 public key",
|
|
243
|
+
)
|
|
244
|
+
signature_verify_parser.add_argument("path")
|
|
245
|
+
signature_verify_parser.add_argument("--signature", required=True)
|
|
246
|
+
signature_verify_parser.add_argument("--public-key", required=True)
|
|
247
|
+
signature_verify_parser.add_argument("--repository-revision")
|
|
248
|
+
signature_verify_parser.add_argument(
|
|
249
|
+
"--json",
|
|
250
|
+
action="store_true",
|
|
251
|
+
dest="json_output",
|
|
252
|
+
help="emit a deterministic machine-readable authentication summary",
|
|
253
|
+
)
|
|
254
|
+
signature_verify_parser.set_defaults(handler=cli_handlers.authoring_evidence_signature_verify)
|
|
255
|
+
|
|
256
|
+
presentation_review_init_parser = subparsers.add_parser(
|
|
257
|
+
"authoring-presentation-review-init",
|
|
258
|
+
help="create a source-bound human-review packet from verified authoring evidence",
|
|
259
|
+
)
|
|
260
|
+
presentation_review_init_parser.add_argument("path")
|
|
261
|
+
presentation_review_init_parser.add_argument("--repository-revision")
|
|
262
|
+
presentation_review_init_parser.add_argument("--output", required=True)
|
|
263
|
+
presentation_review_init_parser.set_defaults(
|
|
264
|
+
handler=cli_handlers.authoring_presentation_review_init
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
presentation_review_finalize_parser = subparsers.add_parser(
|
|
268
|
+
"authoring-presentation-review-finalize",
|
|
269
|
+
help="finalize human decisions into a canonical presentation review",
|
|
270
|
+
)
|
|
271
|
+
presentation_review_finalize_parser.add_argument("path")
|
|
272
|
+
presentation_review_finalize_parser.add_argument("--draft", required=True)
|
|
273
|
+
presentation_review_finalize_parser.add_argument("--repository-revision")
|
|
274
|
+
presentation_review_finalize_parser.add_argument("--output", required=True)
|
|
275
|
+
presentation_review_finalize_parser.set_defaults(
|
|
276
|
+
handler=cli_handlers.authoring_presentation_review_finalize
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
presentation_review_verify_parser = subparsers.add_parser(
|
|
280
|
+
"authoring-presentation-review-verify",
|
|
281
|
+
help="verify a presentation review against exact authoring evidence",
|
|
282
|
+
)
|
|
283
|
+
presentation_review_verify_parser.add_argument("path")
|
|
284
|
+
presentation_review_verify_parser.add_argument("--review", required=True)
|
|
285
|
+
presentation_review_verify_parser.add_argument("--repository-revision")
|
|
286
|
+
presentation_review_verify_parser.add_argument(
|
|
287
|
+
"--json",
|
|
288
|
+
action="store_true",
|
|
289
|
+
dest="json_output",
|
|
290
|
+
help="emit a deterministic machine-readable verification summary",
|
|
291
|
+
)
|
|
292
|
+
presentation_review_verify_parser.set_defaults(
|
|
293
|
+
handler=cli_handlers.authoring_presentation_review_verify
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
presentation_review_sign_parser = subparsers.add_parser(
|
|
297
|
+
"authoring-presentation-review-sign",
|
|
298
|
+
help="authenticate a verified presentation review with Ed25519",
|
|
299
|
+
)
|
|
300
|
+
presentation_review_sign_parser.add_argument("path")
|
|
301
|
+
presentation_review_sign_parser.add_argument("--review", required=True)
|
|
302
|
+
presentation_review_sign_parser.add_argument("--private-key", required=True)
|
|
303
|
+
presentation_review_sign_parser.add_argument("--repository-revision")
|
|
304
|
+
presentation_review_sign_parser.add_argument("--output", required=True)
|
|
305
|
+
presentation_review_sign_parser.set_defaults(
|
|
306
|
+
handler=cli_handlers.authoring_presentation_review_sign
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
presentation_signature_verify_parser = subparsers.add_parser(
|
|
310
|
+
"authoring-presentation-review-signature-verify",
|
|
311
|
+
help="authenticate a presentation review with a trusted Ed25519 public key",
|
|
312
|
+
)
|
|
313
|
+
presentation_signature_verify_parser.add_argument("path")
|
|
314
|
+
presentation_signature_verify_parser.add_argument("--review", required=True)
|
|
315
|
+
presentation_signature_verify_parser.add_argument("--signature", required=True)
|
|
316
|
+
presentation_signature_verify_parser.add_argument("--public-key", required=True)
|
|
317
|
+
presentation_signature_verify_parser.add_argument("--repository-revision")
|
|
318
|
+
presentation_signature_verify_parser.add_argument(
|
|
319
|
+
"--json",
|
|
320
|
+
action="store_true",
|
|
321
|
+
dest="json_output",
|
|
322
|
+
help="emit a deterministic machine-readable authentication summary",
|
|
323
|
+
)
|
|
324
|
+
presentation_signature_verify_parser.set_defaults(
|
|
325
|
+
handler=cli_handlers.authoring_presentation_review_signature_verify
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
promotion_verify_parser = subparsers.add_parser(
|
|
329
|
+
"authoring-promotion-verify",
|
|
330
|
+
help="evaluate promotion eligibility from authenticated authoring artifacts",
|
|
331
|
+
)
|
|
332
|
+
promotion_verify_parser.add_argument("path")
|
|
333
|
+
promotion_verify_parser.add_argument("--review", required=True)
|
|
334
|
+
promotion_verify_parser.add_argument("--signature", required=True)
|
|
335
|
+
promotion_verify_parser.add_argument("--public-key", required=True)
|
|
336
|
+
promotion_verify_parser.add_argument("--repository-revision")
|
|
337
|
+
promotion_verify_parser.add_argument(
|
|
338
|
+
"--json",
|
|
339
|
+
action="store_true",
|
|
340
|
+
dest="json_output",
|
|
341
|
+
help="emit a deterministic machine-readable promotion summary",
|
|
342
|
+
)
|
|
343
|
+
promotion_verify_parser.set_defaults(handler=cli_handlers.authoring_promotion_verify)
|
|
344
|
+
|
|
345
|
+
return parser
|
flowspec2/clock.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Clock contracts and the real UTC adapter used at runtime boundaries."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
|
|
8
|
+
UtcClock = Callable[[], datetime]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def system_utc_now() -> datetime:
|
|
12
|
+
"""Read the real UTC clock for callers that do not inject one."""
|
|
13
|
+
|
|
14
|
+
return datetime.now(timezone.utc)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def utc_timestamp(clock: UtcClock) -> datetime:
|
|
18
|
+
"""Read and normalize an aware clock sample, rejecting ambiguous local time."""
|
|
19
|
+
|
|
20
|
+
timestamp = clock()
|
|
21
|
+
if timestamp.tzinfo is None or timestamp.utcoffset() is None:
|
|
22
|
+
raise ValueError("UTC clock must return a timezone-aware datetime")
|
|
23
|
+
return timestamp.astimezone(timezone.utc)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Compatibility profiles for external workflow formats."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .models import (
|
|
6
|
+
CompatibilityDiagnostic,
|
|
7
|
+
CompatibilityError,
|
|
8
|
+
CompatibilityReport,
|
|
9
|
+
ConversionOutcome,
|
|
10
|
+
)
|
|
11
|
+
from .open_workflow import (
|
|
12
|
+
OPEN_WORKFLOW_PROFILE_ID,
|
|
13
|
+
OPEN_WORKFLOW_SCHEMA_VERSION,
|
|
14
|
+
export_open_workflow,
|
|
15
|
+
import_open_workflow,
|
|
16
|
+
official_open_workflow_schema,
|
|
17
|
+
validate_official_open_workflow,
|
|
18
|
+
validate_open_workflow_profile,
|
|
19
|
+
)
|
|
20
|
+
from .rasa import (
|
|
21
|
+
RASA_FORMAT,
|
|
22
|
+
RASA_MINIMUM_VERSION,
|
|
23
|
+
RASA_PROFILE_VERSION,
|
|
24
|
+
RasaBundle,
|
|
25
|
+
export_rasa,
|
|
26
|
+
import_rasa,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"CompatibilityDiagnostic",
|
|
31
|
+
"CompatibilityError",
|
|
32
|
+
"CompatibilityReport",
|
|
33
|
+
"ConversionOutcome",
|
|
34
|
+
"OPEN_WORKFLOW_PROFILE_ID",
|
|
35
|
+
"OPEN_WORKFLOW_SCHEMA_VERSION",
|
|
36
|
+
"RASA_FORMAT",
|
|
37
|
+
"RASA_MINIMUM_VERSION",
|
|
38
|
+
"RASA_PROFILE_VERSION",
|
|
39
|
+
"RasaBundle",
|
|
40
|
+
"export_open_workflow",
|
|
41
|
+
"export_rasa",
|
|
42
|
+
"import_open_workflow",
|
|
43
|
+
"import_rasa",
|
|
44
|
+
"official_open_workflow_schema",
|
|
45
|
+
"validate_official_open_workflow",
|
|
46
|
+
"validate_open_workflow_profile",
|
|
47
|
+
]
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Shared immutable contracts for compatibility conversions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Generic, Literal, TypeVar
|
|
7
|
+
|
|
8
|
+
DiagnosticSeverity = Literal["warning", "error"]
|
|
9
|
+
Artifact = TypeVar("Artifact")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class CompatibilityDiagnostic:
|
|
14
|
+
"""One stable, machine-readable compatibility finding."""
|
|
15
|
+
|
|
16
|
+
severity: DiagnosticSeverity
|
|
17
|
+
code: str
|
|
18
|
+
source_path: str
|
|
19
|
+
message: str
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(frozen=True)
|
|
23
|
+
class CompatibilityReport:
|
|
24
|
+
"""All findings produced by one source-to-target conversion."""
|
|
25
|
+
|
|
26
|
+
source_format: str
|
|
27
|
+
target_format: str
|
|
28
|
+
diagnostics: tuple[CompatibilityDiagnostic, ...] = ()
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def has_errors(self) -> bool:
|
|
32
|
+
return any(diagnostic.severity == "error" for diagnostic in self.diagnostics)
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def has_warnings(self) -> bool:
|
|
36
|
+
return any(diagnostic.severity == "warning" for diagnostic in self.diagnostics)
|
|
37
|
+
|
|
38
|
+
def blocking_diagnostics(self, *, allow_lossy: bool) -> tuple[CompatibilityDiagnostic, ...]:
|
|
39
|
+
"""Return findings that block serialization under the requested policy."""
|
|
40
|
+
|
|
41
|
+
return tuple(
|
|
42
|
+
diagnostic
|
|
43
|
+
for diagnostic in self.diagnostics
|
|
44
|
+
if diagnostic.severity == "error"
|
|
45
|
+
or (diagnostic.severity == "warning" and not allow_lossy)
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass(frozen=True)
|
|
50
|
+
class ConversionOutcome(Generic[Artifact]):
|
|
51
|
+
"""A converted in-memory artifact and its complete compatibility report."""
|
|
52
|
+
|
|
53
|
+
artifact: Artifact
|
|
54
|
+
report: CompatibilityReport
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class CompatibilityError(ValueError):
|
|
58
|
+
"""Raised when the selected policy blocks a compatibility conversion."""
|
|
59
|
+
|
|
60
|
+
def __init__(
|
|
61
|
+
self,
|
|
62
|
+
report: CompatibilityReport,
|
|
63
|
+
*,
|
|
64
|
+
allow_lossy: bool,
|
|
65
|
+
) -> None:
|
|
66
|
+
self.report = report
|
|
67
|
+
self.allow_lossy = allow_lossy
|
|
68
|
+
diagnostics = report.blocking_diagnostics(allow_lossy=allow_lossy)
|
|
69
|
+
detail = "; ".join(
|
|
70
|
+
f"{diagnostic.code} at {diagnostic.source_path}: {diagnostic.message}"
|
|
71
|
+
for diagnostic in diagnostics
|
|
72
|
+
)
|
|
73
|
+
super().__init__(
|
|
74
|
+
f"{report.source_format} -> {report.target_format} conversion blocked: {detail}"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def enforce_compatibility_policy(report: CompatibilityReport, *, allow_lossy: bool) -> None:
|
|
79
|
+
"""Raise once with the complete report when the conversion is not permitted."""
|
|
80
|
+
|
|
81
|
+
if report.blocking_diagnostics(allow_lossy=allow_lossy):
|
|
82
|
+
raise CompatibilityError(report, allow_lossy=allow_lossy)
|