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.
Files changed (92) hide show
  1. flowspec2/__init__.py +105 -0
  2. flowspec2/authoring/__init__.py +275 -0
  3. flowspec2/authoring/authoring-evidence-signature.schema.json +34 -0
  4. flowspec2/authoring/authoring-evidence.schema.json +455 -0
  5. flowspec2/authoring/authoring-operational-evidence.schema.json +160 -0
  6. flowspec2/authoring/benchmark.py +1409 -0
  7. flowspec2/authoring/corpus/await_correction.case.json +220 -0
  8. flowspec2/authoring/corpus/flowspec2.ctk.json +769 -0
  9. flowspec2/authoring/corpus/gated_derive.case.json +100 -0
  10. flowspec2/authoring/corpus/linear.case.json +55 -0
  11. flowspec2/authoring/corpus/manifest.json +31 -0
  12. flowspec2/authoring/corpus/subflow.case.json +66 -0
  13. flowspec2/authoring/corpus/terminal.case.json +94 -0
  14. flowspec2/authoring/corpus.py +307 -0
  15. flowspec2/authoring/ctk.py +836 -0
  16. flowspec2/authoring/detached_signature.py +120 -0
  17. flowspec2/authoring/evidence.py +311 -0
  18. flowspec2/authoring/evidence_signature.py +187 -0
  19. flowspec2/authoring/evidence_verification.py +229 -0
  20. flowspec2/authoring/gemini.py +215 -0
  21. flowspec2/authoring/operational-corpus.json +61 -0
  22. flowspec2/authoring/operational.py +956 -0
  23. flowspec2/authoring/operational_providers.py +167 -0
  24. flowspec2/authoring/presentation_review.py +1013 -0
  25. flowspec2/authoring/presentation_review_signature.py +305 -0
  26. flowspec2/authoring/projection.py +299 -0
  27. flowspec2/authoring/provider_prompt.py +83 -0
  28. flowspec2/backends/__init__.py +82 -0
  29. flowspec2/backends/http.py +189 -0
  30. flowspec2/checker.py +238 -0
  31. flowspec2/cli.py +789 -0
  32. flowspec2/cli_parser.py +345 -0
  33. flowspec2/clock.py +23 -0
  34. flowspec2/compat/__init__.py +47 -0
  35. flowspec2/compat/models.py +82 -0
  36. flowspec2/compat/open_workflow.py +616 -0
  37. flowspec2/compat/rasa.py +19 -0
  38. flowspec2/compat/rasa_export.py +876 -0
  39. flowspec2/compat/rasa_import.py +992 -0
  40. flowspec2/compat/rasa_shared.py +270 -0
  41. flowspec2/compat/schemas/open-workflow-conversation-1.schema.json +138 -0
  42. flowspec2/compat/schemas/vendor/open-workflow-1.0.3.LICENSE +201 -0
  43. flowspec2/compat/schemas/vendor/open-workflow-1.0.3.provenance.json +13 -0
  44. flowspec2/compat/schemas/vendor/open-workflow-1.0.3.workflow.yaml +1956 -0
  45. flowspec2/compat/tool_profiles.py +149 -0
  46. flowspec2/compat/yaml.py +147 -0
  47. flowspec2/compiler.py +957 -0
  48. flowspec2/compiler_contracts.py +17 -0
  49. flowspec2/compiler_resume_contracts.py +594 -0
  50. flowspec2/compiler_schema_relations.py +1830 -0
  51. flowspec2/compiler_tool_contracts.py +245 -0
  52. flowspec2/compiler_value_contracts.py +447 -0
  53. flowspec2/derive.py +32 -0
  54. flowspec2/diagnostics.py +94 -0
  55. flowspec2/domains.py +489 -0
  56. flowspec2/experimental/__init__.py +57 -0
  57. flowspec2/experimental/flowspec-3-draft.schema.json +923 -0
  58. flowspec2/experimental/v3-preview-loss-policy.json +161 -0
  59. flowspec2/experimental/v3_lowering.py +928 -0
  60. flowspec2/experimental/v3_preview.py +2460 -0
  61. flowspec2/flowspec-2.schema.json +635 -0
  62. flowspec2/interactive.py +300 -0
  63. flowspec2/ir.py +1459 -0
  64. flowspec2/json_codec.py +120 -0
  65. flowspec2/llm.py +366 -0
  66. flowspec2/models.py +121 -0
  67. flowspec2/nodes.py +1537 -0
  68. flowspec2/observability.py +151 -0
  69. flowspec2/predicates.py +89 -0
  70. flowspec2/profiles.py +118 -0
  71. flowspec2/py.typed +0 -0
  72. flowspec2/runtime.py +1059 -0
  73. flowspec2/schema.py +54 -0
  74. flowspec2/schema_contracts.py +203 -0
  75. flowspec2/semantic_derive_contracts.py +530 -0
  76. flowspec2/semantic_path_contracts.py +528 -0
  77. flowspec2/semantic_predicate_contracts.py +355 -0
  78. flowspec2/semantic_schema_contracts.py +137 -0
  79. flowspec2/semantic_source_contracts.py +21 -0
  80. flowspec2/semantic_state_contracts.py +450 -0
  81. flowspec2/semantic_support.py +40 -0
  82. flowspec2/semantics.py +410 -0
  83. flowspec2/state_migration.py +1034 -0
  84. flowspec2/subflows/__init__.py +1117 -0
  85. flowspec2/subflows/address.py +328 -0
  86. flowspec2/subflows/identification.py +1031 -0
  87. flowspec2/tools.py +1154 -0
  88. flowspec2-1.0.0.dist-info/METADATA +482 -0
  89. flowspec2-1.0.0.dist-info/RECORD +92 -0
  90. flowspec2-1.0.0.dist-info/WHEEL +4 -0
  91. flowspec2-1.0.0.dist-info/entry_points.txt +2 -0
  92. flowspec2-1.0.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,635 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://wllsena.github.io/flowspec2/schemas/flowspec-2.json",
4
+ "title": "flowspec/2 — declarative conversational flow definition compiled to LangGraph",
5
+ "description": "A single self-contained JSON document describing one citizen-facing service flow. The running ReAct agent loads it and compiles a StateGraph[ServiceState] inside the multi_step_service MCP tool. RAIL fields (the deterministic structure) pin states, slot value-domains, transitions, guards, tool bindings, interrupts, idempotency/reset and guardrails. LLM-FREEDOM fields (non-deterministic execution within the rails) are ONLY: route.description, route.trigger_phrases, every prompt.text (unless verbatim:true), domains.*.normalize hints, and the capabilities the LLM MAY invoke. The boundary is positional: by FIELD (only prompt.text/extract_hint/route.* are LLM-editable), by LOCATION (path/overrides/uses are rails; capabilities is the fenced LLM zone), and by CONSTRUCT (the closed domain IS the boundary — the LLM extracts a candidate, the generated validator rejects out-of-domain).",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["schema", "flow", "version", "route", "domains", "path"],
9
+ "properties": {
10
+ "schema": { "description": "RAIL. Format identifier + major version of THIS spec language (not the flow).", "const": "flowspec/2" },
11
+ "flow": { "description": "RAIL. Stable flow id == service_name; StateManager + checkpointer + config.yaml flow.registry key. Immutable across versions.", "type": "string", "pattern": "^[a-z][a-z0-9_]*$" },
12
+ "version": { "description": "RAIL. Semver of this flow document. Bump per change; diff-friendly.", "type": "string", "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" },
13
+ "service": {
14
+ "description": "RAIL. First-class service-identity metadata, decoupled from executable nodes. Carries service catalog codes, slugs, and knowledge identifiers; authors declare these values here instead of synthesizing an initialization node.",
15
+ "type": "object", "additionalProperties": false,
16
+ "properties": {
17
+ "id": { "type": "string", "description": "ticketing system/1746 service id, e.g. '18131'." },
18
+ "service_code": { "type": "string" },
19
+ "knowledge_service_id": { "type": "string", "description": "Knowledge-hub id for best-effort context load." },
20
+ "identification_required": { "type": "boolean", "default": false },
21
+ "slugs": { "type": "array", "items": { "type": "string" }, "description": "RAIL. Catalog/service-detail slug(s) this flow serves. A host app auto-exposes the guided flow for any service whose slug is listed here, so adding a guided flow is just dropping a JSON with its slug(s) — no host code change. Several slugs may point at one flow." }
22
+ }
23
+ },
24
+ "route": {
25
+ "description": "LLM-FREEDOM surface. The ONLY hook the top-level ReAct LLM uses to enter this flow. description = the routing hook (non-deterministic route-in). trigger_phrases = a versionable seed, NOT the sole signal. entry_args_schema = the closed set of initial slots the LLM may pass on entry (routing metadata only, never app state).",
26
+ "type": "object", "additionalProperties": false, "required": ["description"],
27
+ "properties": {
28
+ "description": { "type": "string", "minLength": 1 },
29
+ "trigger_phrases": { "type": "array", "items": { "type": "string" } },
30
+ "entry_args_schema": { "description": "JSON-Schema object of slots permitted on flow entry. Keys must be declared slots.", "type": "object" }
31
+ }
32
+ },
33
+ "config": {
34
+ "description": "RAIL. Guardrail booleans consumed by subflows and the engine. Deterministically force skip/default/end. No LLM hook.",
35
+ "type": "object", "additionalProperties": false,
36
+ "properties": {
37
+ "address_required": { "type": "boolean", "default": false },
38
+ "identification_required": { "type": "boolean", "default": false },
39
+ "max_attempts": { "type": "integer", "minimum": 1, "default": 3 }
40
+ }
41
+ },
42
+ "entry": {
43
+ "description": "RAIL. Generic best-effort, non-terminal entry side effect. Runs once before the path; never asks, blocks, or resets. Service identity remains in the service contract instead of this executable hook.",
44
+ "type": "object", "additionalProperties": false, "required": ["tool"],
45
+ "properties": {
46
+ "tool": { "$ref": "#/$defs/identifier", "description": "Registered tool name to call on entry." },
47
+ "blocking": { "type": "boolean", "default": false, "description": "false = failure is swallowed and the flow proceeds." },
48
+ "writes": { "$ref": "#/$defs/identifier", "description": "Data-partition key under which the result is stored." }
49
+ }
50
+ },
51
+ "domains": {
52
+ "description": "RAIL + the SPINE. The closed value-domain registry materializes the Pydantic before-validator, AgentResponse.payload_schema, interactive button titles, and interactive list rows. Categorical options come from values/rows; bool options expose true/false with Yes/No titles. Declaring a domain once is the entire structure/execution boundary: values are the rail and free-text-to-token extraction is LLM work. The validator prevents widening the domain. normalize is the only LLM-facing hint and is still deterministically enforced.",
53
+ "type": "object", "minProperties": 1,
54
+ "propertyNames": { "$ref": "#/$defs/domainIdentifier" },
55
+ "additionalProperties": {
56
+ "description": "Closed union by domain kind. A kind accepts only fields consumed by its validator.",
57
+ "oneOf": [
58
+ {
59
+ "title": "Categorical domain",
60
+ "type": "object", "additionalProperties": false, "required": ["values"],
61
+ "properties": {
62
+ "type": { "const": "categorical", "default": "categorical" },
63
+ "values": { "description": "Ordered closed token set. null is an explicit member.", "type": "array", "minItems": 1, "uniqueItems": true, "items": { "oneOf": [{ "type": "string", "minLength": 1 }, { "type": "null" }] } },
64
+ "rows": {
65
+ "description": "Optional per-token presentation subtitles. Semantic linking requires each row value to belong to values and to appear once.",
66
+ "type": "array",
67
+ "items": { "type": "object", "additionalProperties": false, "required": ["value"], "properties": { "value": { "type": "string", "minLength": 1 }, "description": { "type": "string" } } }
68
+ },
69
+ "normalize": {
70
+ "description": "Deterministic free-speech to closed-token coercion.",
71
+ "type": "object", "additionalProperties": false,
72
+ "properties": {
73
+ "accent_fold": { "type": "boolean", "default": false },
74
+ "number_words": { "type": "boolean", "default": false, "description": "Map supported number words to the positional token." },
75
+ "synonyms": { "description": "Free-text alias to a canonical categorical token.", "type": "object", "propertyNames": { "minLength": 1 }, "additionalProperties": { "type": ["string", "null"] } }
76
+ }
77
+ }
78
+ }
79
+ },
80
+ {
81
+ "title": "Boolean domain",
82
+ "type": "object", "additionalProperties": false, "required": ["type"],
83
+ "properties": {
84
+ "type": { "const": "bool" },
85
+ "normalize": {
86
+ "description": "Deterministic boolean coercion and aliases.",
87
+ "type": "object", "additionalProperties": false,
88
+ "properties": {
89
+ "accent_fold": { "type": "boolean", "default": false },
90
+ "affirmation": { "type": "boolean", "default": false },
91
+ "emoji_veto": { "type": "boolean", "default": false },
92
+ "synonyms": { "description": "Free-text alias to a canonical boolean.", "type": "object", "propertyNames": { "minLength": 1 }, "additionalProperties": { "type": "boolean" } }
93
+ },
94
+ "allOf": [
95
+ {
96
+ "if": { "properties": { "emoji_veto": { "const": true } }, "required": ["emoji_veto"] },
97
+ "then": { "properties": { "affirmation": { "const": true } }, "required": ["affirmation"] }
98
+ }
99
+ ]
100
+ }
101
+ }
102
+ },
103
+ {
104
+ "title": "Free-text domain",
105
+ "type": "object", "additionalProperties": false, "required": ["type"],
106
+ "properties": {
107
+ "type": { "const": "free_text" },
108
+ "optional": { "description": "Accept an empty answer as an explicit empty string.", "type": "boolean", "default": false }
109
+ }
110
+ },
111
+ {
112
+ "title": "Brazilian tax ID domain",
113
+ "type": "object", "additionalProperties": false, "required": ["type"],
114
+ "properties": { "type": { "const": "brazilian_tax_id" } }
115
+ },
116
+ {
117
+ "title": "Email domain",
118
+ "type": "object", "additionalProperties": false, "required": ["type"],
119
+ "properties": { "type": { "const": "email" } }
120
+ },
121
+ {
122
+ "title": "Name domain",
123
+ "type": "object", "additionalProperties": false, "required": ["type"],
124
+ "properties": { "type": { "const": "name" } }
125
+ },
126
+ {
127
+ "title": "Integer domain",
128
+ "type": "object", "additionalProperties": false, "required": ["type"],
129
+ "properties": {
130
+ "type": { "const": "integer" },
131
+ "minimum": { "type": "number", "description": "Inclusive lower bound." },
132
+ "maximum": { "type": "number", "description": "Inclusive upper bound." }
133
+ }
134
+ },
135
+ {
136
+ "title": "Number domain",
137
+ "type": "object", "additionalProperties": false, "required": ["type"],
138
+ "properties": {
139
+ "type": { "const": "number" },
140
+ "minimum": { "type": "number", "description": "Inclusive lower bound." },
141
+ "maximum": { "type": "number", "description": "Inclusive upper bound." }
142
+ }
143
+ }
144
+ ]
145
+ }
146
+ },
147
+ "slots": {
148
+ "description": "RAIL. Per-slot declarations referenced by path steps, interactive options, and corrections. Binds a stable state key to a domain + persistence partition + collection guardrails. Position is rediscovered from filled slots; NO step pointer is persisted.",
149
+ "type": "object",
150
+ "propertyNames": { "$ref": "#/$defs/identifier" },
151
+ "additionalProperties": {
152
+ "type": "object", "additionalProperties": false, "required": ["domain"],
153
+ "properties": {
154
+ "domain": { "$ref": "#/$defs/domainIdentifier", "description": "References a key in domains{}." },
155
+ "persist": { "description": "Which persistent ServiceState partition owns the validated slot. 'data' is agent-visible; 'internal' is hidden.", "enum": ["data", "internal"], "default": "data" },
156
+ "required": { "type": "boolean", "default": false, "description": "When true, a gate-open collector requires a domain-valid value; explicit null is accepted only when nullable is true. When false, explicit null deterministically skips collection without storing a slot value." },
157
+ "nullable": { "type": "boolean", "default": false, "description": "Explicit null is a domain-valid stored value. This is distinct from the no-value skip performed by required:false." },
158
+ "requires": { "description": "RAIL. Hard precedence + dependency edges: these slots must be filled before this one is asked, AND this slot's value is invalidated (cleared) when any is corrected. The clear-cascade is DERIVED from this — author declares NOTHING extra (footgun killer).", "type": "array", "uniqueItems": true, "items": { "$ref": "#/$defs/identifier" } },
159
+ "prefill_sources": { "description": "Channels allowed to pre-fill this slot without its own prompt when fill_only_when_asked is true. e.g. whatsapp_flow, govbr_token.", "type": "array", "minItems": 1, "uniqueItems": true, "items": { "$ref": "#/$defs/identifier" } },
160
+ "max_attempts": { "type": "integer", "minimum": 1, "description": "Re-ask budget; engine owns the counter. Defaults to config.max_attempts." },
161
+ "on_exhaust": { "description": "After max_attempts: reask, skip without storing a value, store default, handoff, or END.", "enum": ["reask", "skip", "default", "handoff", "END"], "default": "reask" },
162
+ "default": { "description": "Fallback value stored only by on_exhaust:default. The compiler validates and normalizes it against the slot domain; null therefore requires a nullable domain." },
163
+ "fill_only_when_asked": { "description": "RAIL (single declarative flag, fixes A9). When true, the engine accepts this slot only after its own prompt or from a channel listed in prefill_sources. NOT a rule engine.", "type": "boolean", "default": false }
164
+ },
165
+ "allOf": [
166
+ {
167
+ "if": {
168
+ "properties": { "on_exhaust": { "const": "default" } },
169
+ "required": ["on_exhaust"]
170
+ },
171
+ "then": { "required": ["default"] },
172
+ "else": { "not": { "required": ["default"] } }
173
+ },
174
+ {
175
+ "if": {
176
+ "properties": { "nullable": { "const": true } },
177
+ "required": ["nullable"]
178
+ },
179
+ "then": {
180
+ "properties": { "required": { "const": true } },
181
+ "required": ["required"]
182
+ }
183
+ },
184
+ {
185
+ "if": { "required": ["prefill_sources"] },
186
+ "then": {
187
+ "properties": { "fill_only_when_asked": { "const": true } },
188
+ "required": ["fill_only_when_asked"]
189
+ }
190
+ }
191
+ ]
192
+ }
193
+ },
194
+ "path": {
195
+ "description": "RAIL. The flat ordered happy path. Implicit fall-through: each step is one StateGraph node; linear order is the default edge; the PAUSE->END and back-to-agent edges are synthesized. The step pointer is never serialized; position is rediscovered from state. A step is exactly one of slot | confirm | derive | terminal | use | await_external.",
196
+ "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/step" }
197
+ },
198
+ "uses": {
199
+ "description": "RAIL + the MINIMALITY lever. Versioned reusable subflows (mixins) composed by id@major + config flags. Splices the subflow's slots+nodes+routers at its anchor in path; inherits invisible idempotency/reset/max-attempt defaults so a new author cannot forget them. The compiler discovers each subflow's exposed slots and validates corrections, requirements, derives, and terminal mappings after expansion.",
200
+ "type": "array", "items": { "$ref": "#/$defs/subflowRef" }
201
+ },
202
+ "derive": {
203
+ "description": "RAIL. Computed values, never asked (escape hatch #1). Lookup-table-ONLY with a single $from[i] fallback — deliberately too weak to be an expression language. Inserted as a no-prompt node that always advances. Prevents value-domain leak: the LLM never computes a classification.",
204
+ "type": "array", "items": { "$ref": "#/$defs/deriveDef" }
205
+ },
206
+ "confirm": {
207
+ "description": "RAIL. The data-confirmation state + non-linear correction routing (escape hatch #2). On confirmation -> terminal. The constrained decoder emits the exact canonical identifier of one correctable slot; the compiler routes to that slot's node and clears it + its transitive requires[]-dependents + derives that read them. Author lists only the directly-correctable slots; the cascade is derived.",
208
+ "$ref": "#/$defs/confirmDef"
209
+ },
210
+ "terminal": {
211
+ "description": "RAIL. The single fulfillment action + lifecycle (idempotency/reset/empty-payload). Reachable only when every required gate-open slot is filled. idempotent:true is one flag; the sha256 key recipe + retryable-exception taxonomy stay in the library. Outcome TRICHOTOMY: success (reset_next), retryable (preserve_state, re-enter), fatal (reset_next).",
212
+ "$ref": "#/$defs/terminalDef"
213
+ },
214
+ "overrides": {
215
+ "description": "RAIL. The hard-case tier (escape hatches), kept OUT of the linear path so the happy-path read stays clean. Only flows that need them pay. A simple linear service omits this entirely.",
216
+ "type": "object", "additionalProperties": false,
217
+ "properties": {
218
+ "gates": { "description": "Multi-slot gate predicates keyed by step id. gate==false => the step's slot is satisfied-by-vacuity (skipped uniformly, no special skip-node). Uses the frozen object grammar.", "type": "object", "propertyNames": { "$ref": "#/$defs/identifier" }, "additionalProperties": { "$ref": "#/$defs/predicate" } }
219
+ }
220
+ },
221
+ "auto_flow": {
222
+ "description": "RAIL. Pre-graph WhatsApp Flow gate (NOT a state — it suspends on nfm_reply, not the next text turn). If send_when holds and the entry slot is empty, the compiler sends the Meta Flow envelope and returns status:flow_sent WITHOUT entering the graph. On submission the nfm_reply is re-injected as payload._source=whatsapp_flow and resumed at resume_at. alias_map handles one-Flow-field -> multiple-slots fan-out (fixes A4) bound HARD to a flat value-keyed map (no nested logic).",
223
+ "$ref": "#/$defs/autoFlowDef"
224
+ },
225
+ "capabilities": {
226
+ "description": "FENCED LLM-FREEDOM zone (boundary by LOCATION). Agent-scope non-deterministic side-actions the LLM MAY invoke at its discretion, bounded by the Mule outbound whitelist (cross-checked by the COMPILER against config.yaml, never encoded here). await_external is the one reusable out-of-band primitive: it is bound either to an explicit path marker or to a subflow-provided step.",
227
+ "type": "object", "additionalProperties": false,
228
+ "properties": {
229
+ "media_in": { "type": "object", "additionalProperties": false, "properties": { "analyze": { "type": "array", "minItems": 1, "uniqueItems": true, "items": { "enum": ["image", "audio", "video"] } }, "route_by": { "$ref": "#/$defs/identifier" } } },
230
+ "media_out": {
231
+ "description": "Outbound media types the LLM may send. A template entry carries a Meta 24h-window template name + variable bindings (fixes A7). Plain types are bare strings.",
232
+ "type": "array", "minItems": 1, "uniqueItems": true,
233
+ "items": {
234
+ "oneOf": [
235
+ { "enum": ["image", "video", "audio", "document", "location", "contacts"] },
236
+ { "type": "object", "additionalProperties": false, "required": ["template"], "properties": { "template": { "type": "object", "additionalProperties": false, "required": ["name"], "properties": { "name": { "type": "string" }, "vars": { "type": "array", "items": { "type": "string" } } } } } }
237
+ ]
238
+ }
239
+ },
240
+ "tts": { "type": "boolean", "default": false },
241
+ "location_in": { "type": "boolean", "default": false },
242
+ "handoff": { "type": "object", "additionalProperties": false, "properties": { "target": { "type": "string", "description": "e.g. '1746' or a human queue." }, "when": { "enum": ["llm_decides"], "default": "llm_decides" } } },
243
+ "session_reset": { "type": "boolean", "default": false },
244
+ "await_external": {
245
+ "description": "First-class out-of-band suspend/resume primitive (gov.br OAuth, payment redirect, alternate IdP). Emits an out-of-band host marker with an absolute deadline when timeout is configured, suspends on an EXTERNAL signal (not the next text turn), and resumes with atomic bounded mappings. Timeout is delivered by the host as _external_event=timeout; the runtime owns no scheduler.",
246
+ "type": "object", "additionalProperties": false, "required": ["kind", "resume_on"],
247
+ "dependentRequired": { "timeout_seconds": ["timeout"] },
248
+ "properties": {
249
+ "kind": { "enum": ["cta_url"] },
250
+ "step": { "$ref": "#/$defs/identifier", "description": "Stable id of a subflow-provided await node. An explicit path await_external step may supply the same id instead." },
251
+ "resume_on": { "$ref": "#/$defs/identifier", "description": "External signal payload key, e.g. govbr_token." },
252
+ "resume": { "$ref": "#/$defs/resumeContractDef", "description": "Versioned token, correlation, replay, and late-delivery contract. Required by strict runtime profiles; omission identifies a legacy capability." },
253
+ "prompt": { "$ref": "#/$defs/promptDef" },
254
+ "interactive": {
255
+ "allOf": [
256
+ { "$ref": "#/$defs/interactiveDef" },
257
+ { "properties": { "kind": { "const": "cta_url" }, "out_of_band": { "const": true } } }
258
+ ]
259
+ },
260
+ "on_resume": {
261
+ "type": "object", "additionalProperties": false,
262
+ "properties": {
263
+ "set": { "$ref": "#/$defs/stateBindingMap", "description": "State key -> literal or $token.path binding. Resolved writes are committed atomically." },
264
+ "enrich": {
265
+ "description": "Explicit enrichment call. A bare tool name remains accepted for legacy subflow documents; new documents declare the object contract.",
266
+ "oneOf": [
267
+ { "type": "string", "minLength": 1 },
268
+ { "$ref": "#/$defs/enrichmentDef" }
269
+ ]
270
+ }
271
+ }
272
+ },
273
+ "timeout": { "$ref": "#/$defs/transition" },
274
+ "timeout_seconds": { "type": "integer", "exclusiveMinimum": 0, "description": "Relative duration used to materialize the absolute host deadline." },
275
+ "max_resends": { "type": "integer", "minimum": 0, "description": "Optional runtime-enforced budget for accepted resend recovery events. Omission leaves resend limiting to the host." },
276
+ "recovery": { "type": "object", "additionalProperties": false, "properties": { "abort": { "$ref": "#/$defs/transition" }, "resend": { "$ref": "#/$defs/transition" }, "switch": { "$ref": "#/$defs/transition" } } }
277
+ },
278
+ "allOf": [
279
+ {
280
+ "if": { "required": ["timeout"] },
281
+ "then": { "properties": { "timeout_seconds": { "default": 900 } } }
282
+ },
283
+ {
284
+ "if": { "required": ["max_resends"] },
285
+ "then": {
286
+ "required": ["recovery"],
287
+ "properties": { "recovery": { "required": ["resend"] } }
288
+ }
289
+ }
290
+ ]
291
+ }
292
+ }
293
+ }
294
+ },
295
+ "$defs": {
296
+ "identifier": {
297
+ "type": "string",
298
+ "pattern": "^[a-z][a-z0-9_]*$"
299
+ },
300
+ "domainIdentifier": {
301
+ "type": "string",
302
+ "pattern": "^[A-Za-z][A-Za-z0-9_]*$"
303
+ },
304
+ "fieldIdentifier": {
305
+ "type": "string",
306
+ "pattern": "^[A-Za-z][A-Za-z0-9_]*$"
307
+ },
308
+ "promptDef": {
309
+ "description": "Citizen-facing fallback text. text is LLM-editable (the LLM may rephrase) UNLESS verbatim:true, which pins legally/contractually fixed wording (gov.br CTA, 1746 disclaimer). extract_hint is LLM guidance for free-text->token extraction (LLM-freedom).",
310
+ "type": "object", "additionalProperties": false, "required": ["text"],
311
+ "properties": { "text": { "type": "string" }, "verbatim": { "type": "boolean", "default": false }, "extract_hint": { "type": "string" } }
312
+ },
313
+ "predicate": {
314
+ "description": "Closed object-form predicate grammar. Read-only namespaces: slots.<name>, internal.<name>, payload.<key>, config.<flag>, address.<field>. Operators: in, eq, ne, is_present, and, or, not. Canonical flows do not accept string-expression shorthand. null is a valid literal.",
315
+ "type": "object", "minProperties": 1, "maxProperties": 1, "additionalProperties": false,
316
+ "properties": {
317
+ "in": { "type": "array", "minItems": 2, "maxItems": 2, "prefixItems": [ { "$ref": "#/$defs/stateReference" }, { "type": "array", "items": { "type": ["string", "number", "boolean", "null"] } } ], "items": false },
318
+ "eq": { "type": "array", "minItems": 2, "maxItems": 2, "items": { "$ref": "#/$defs/predicateOperand" } },
319
+ "ne": { "type": "array", "minItems": 2, "maxItems": 2, "items": { "$ref": "#/$defs/predicateOperand" } },
320
+ "is_present": { "$ref": "#/$defs/stateReference" },
321
+ "and": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/predicate" } },
322
+ "or": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/predicate" } },
323
+ "not": { "$ref": "#/$defs/predicate" }
324
+ }
325
+ },
326
+ "predicateOperand": {
327
+ "description": "A scalar operand. A bare namespaced string is always a state reference; wrap it as {literal: ...} when that exact string is the intended literal.",
328
+ "oneOf": [
329
+ { "type": ["string", "number", "boolean", "null"] },
330
+ {
331
+ "type": "object", "additionalProperties": false, "required": ["literal"],
332
+ "properties": { "literal": { "type": ["string", "number", "boolean", "null"] } }
333
+ }
334
+ ]
335
+ },
336
+ "stateReference": {
337
+ "description": "Namespaced state reference; true iff its resolved value is filled and non-null.",
338
+ "type": "string",
339
+ "oneOf": [
340
+ { "pattern": "^slots\\.[a-z][a-z0-9_]*$" },
341
+ { "pattern": "^(internal|payload|config)\\.[A-Za-z_][A-Za-z0-9_]*$" },
342
+ { "pattern": "^address\\.[A-Za-z_][A-Za-z0-9_]*(?:\\.[A-Za-z_][A-Za-z0-9_]*)*$" }
343
+ ]
344
+ },
345
+ "interactiveDef": {
346
+ "description": "RAIL. Closed UI union. Choice variants materialize options from the target domain. The CTA variant is reserved for external waits.",
347
+ "oneOf": [
348
+ {
349
+ "title": "Buttons",
350
+ "type": "object", "additionalProperties": false, "required": ["kind", "field", "from_domain"],
351
+ "properties": {
352
+ "kind": { "const": "buttons" },
353
+ "field": { "$ref": "#/$defs/fieldIdentifier" },
354
+ "from_domain": { "$ref": "#/$defs/domainIdentifier" },
355
+ "options_when": { "$ref": "#/$defs/conditionalOptions" }
356
+ }
357
+ },
358
+ {
359
+ "title": "List",
360
+ "type": "object", "additionalProperties": false, "required": ["kind", "field", "from_domain"],
361
+ "properties": {
362
+ "kind": { "const": "list" },
363
+ "field": { "$ref": "#/$defs/fieldIdentifier" },
364
+ "from_domain": { "$ref": "#/$defs/domainIdentifier" },
365
+ "options_when": { "$ref": "#/$defs/conditionalOptions" }
366
+ }
367
+ },
368
+ {
369
+ "title": "Out-of-band CTA",
370
+ "type": "object", "additionalProperties": false, "required": ["kind", "field", "out_of_band"],
371
+ "properties": {
372
+ "kind": { "const": "cta_url" },
373
+ "field": { "$ref": "#/$defs/fieldIdentifier" },
374
+ "out_of_band": { "const": true },
375
+ "next_step": { "$ref": "#/$defs/identifier" }
376
+ }
377
+ }
378
+ ]
379
+ },
380
+ "conditionalOptions": {
381
+ "description": "Conditional visibility for canonical rendered options.",
382
+ "type": "array", "uniqueItems": true,
383
+ "items": {
384
+ "type": "object", "additionalProperties": false, "required": ["value", "gate"],
385
+ "properties": {
386
+ "value": { "type": ["string", "boolean"] },
387
+ "gate": { "$ref": "#/$defs/predicate" }
388
+ }
389
+ }
390
+ },
391
+ "choiceInteractiveDef": {
392
+ "allOf": [
393
+ { "$ref": "#/$defs/interactiveDef" },
394
+ { "properties": { "kind": { "enum": ["buttons", "list"] } } }
395
+ ]
396
+ },
397
+ "step": {
398
+ "description": "RAIL. A closed union of path-step contracts. Each kind exposes only fields the compiler consumes.",
399
+ "oneOf": [
400
+ {
401
+ "title": "Collect step",
402
+ "type": "object", "additionalProperties": false, "required": ["slot"],
403
+ "properties": {
404
+ "step": { "$ref": "#/$defs/identifier", "description": "Stable node id. Defaults to the slot name." },
405
+ "slot": { "$ref": "#/$defs/identifier", "description": "References a declared slot and pauses while it is unfilled." },
406
+ "prompt": { "$ref": "#/$defs/promptDef" },
407
+ "interactive": { "$ref": "#/$defs/choiceInteractiveDef" },
408
+ "ask_when": { "$ref": "#/$defs/predicate", "description": "Ask only when this predicate is true." },
409
+ "skip_when": { "$ref": "#/$defs/predicate", "description": "Skip this collection when this predicate is true." }
410
+ }
411
+ },
412
+ {
413
+ "title": "Confirmation step",
414
+ "type": "object", "additionalProperties": false, "required": ["confirm"],
415
+ "properties": {
416
+ "step": { "$ref": "#/$defs/identifier", "description": "Stable node id. Defaults to the confirmation slot name." },
417
+ "confirm": { "$ref": "#/$defs/identifier", "description": "References a declared boolean confirmation slot." },
418
+ "prompt": { "$ref": "#/$defs/promptDef" },
419
+ "interactive": { "$ref": "#/$defs/choiceInteractiveDef" },
420
+ "ask_when": { "$ref": "#/$defs/predicate", "description": "Ask only when this predicate is true." },
421
+ "skip_when": { "$ref": "#/$defs/predicate", "description": "Skip this confirmation when this predicate is true." },
422
+ "on_reject": { "description": "Ends the flow with a message when the citizen declines.", "type": "object", "additionalProperties": false, "required": ["end"], "properties": { "end": { "type": "string", "minLength": 1 } } },
423
+ "correctable": { "const": true, "description": "Marks this confirmation as the correction hub." }
424
+ },
425
+ "allOf": [
426
+ { "not": { "required": ["on_reject", "correctable"] } },
427
+ {
428
+ "if": { "required": ["correctable"] },
429
+ "then": {
430
+ "not": {
431
+ "anyOf": [
432
+ { "required": ["ask_when"] },
433
+ { "required": ["skip_when"] }
434
+ ]
435
+ }
436
+ }
437
+ },
438
+ {
439
+ "if": { "required": ["on_reject"] },
440
+ "then": { "not": { "required": ["ask_when"] } }
441
+ },
442
+ {
443
+ "if": {
444
+ "not": {
445
+ "anyOf": [
446
+ { "required": ["on_reject"] },
447
+ { "required": ["correctable"] }
448
+ ]
449
+ }
450
+ },
451
+ "then": { "not": { "required": ["skip_when"] } }
452
+ }
453
+ ]
454
+ },
455
+ {
456
+ "title": "Derivation step",
457
+ "type": "object", "additionalProperties": false, "required": ["derive"],
458
+ "properties": {
459
+ "step": { "$ref": "#/$defs/identifier", "description": "Stable node id. Defaults to derive_<target>." },
460
+ "derive": { "$ref": "#/$defs/identifier", "description": "References a derive definition by its writes target." }
461
+ }
462
+ },
463
+ {
464
+ "title": "Terminal step",
465
+ "type": "object", "additionalProperties": false, "required": ["terminal"],
466
+ "properties": {
467
+ "terminal": { "const": true, "description": "Binds the top-level terminal action at this position." }
468
+ }
469
+ },
470
+ {
471
+ "title": "Subflow step",
472
+ "type": "object", "additionalProperties": false, "required": ["use"],
473
+ "properties": {
474
+ "use": { "type": "string", "minLength": 1, "description": "References a declared versioned subflow use." }
475
+ }
476
+ },
477
+ {
478
+ "title": "External-wait step",
479
+ "type": "object", "additionalProperties": false, "required": ["await_external"],
480
+ "properties": {
481
+ "step": { "$ref": "#/$defs/identifier", "description": "Stable node id shared with the external-wait capability." },
482
+ "await_external": { "const": true, "description": "Binds the singular external-wait capability at this position." },
483
+ "prompt": { "$ref": "#/$defs/promptDef" },
484
+ "interactive": {
485
+ "allOf": [
486
+ { "$ref": "#/$defs/interactiveDef" },
487
+ { "properties": { "kind": { "const": "cta_url" }, "out_of_band": { "const": true } } }
488
+ ]
489
+ }
490
+ }
491
+ }
492
+ ]
493
+ },
494
+ "subflowRef": {
495
+ "description": "RAIL. A versioned mixin reference. ref = name@major (visible, diffable version event). with{} flips the subflow's common_config rails. The subflow brings its own slots/nodes/await_external and inherits idempotency/reset defaults.",
496
+ "type": "object", "additionalProperties": false, "required": ["ref"],
497
+ "properties": {
498
+ "ref": { "type": "string", "pattern": "^[a-z][a-z0-9_]*@[0-9]+$" },
499
+ "with": {
500
+ "description": "Configuration is structurally an object; the resolved subflow manifest is the single source of truth for its closed keys and values.",
501
+ "type": "object"
502
+ }
503
+ }
504
+ },
505
+ "deriveDef": {
506
+ "description": "RAIL. A computed value. Lookup table keyed by '|'.join(from-values) (null member => empty segment, e.g. 'Hanging||'). default is a literal or the single sigil $from[i] (the i-th source value). after = the step id this derive runs immediately after.",
507
+ "type": "object", "additionalProperties": false, "required": ["writes", "from", "lookup"],
508
+ "properties": {
509
+ "writes": { "$ref": "#/$defs/identifier", "description": "data key the result is written to (a derived slot, never asked)." },
510
+ "from": { "type": "array", "minItems": 1, "uniqueItems": true, "items": { "$ref": "#/$defs/identifier" }, "description": "ordered source slot names." },
511
+ "after": { "$ref": "#/$defs/identifier", "description": "step id after which this derive node runs." },
512
+ "lookup": { "description": "key ('|'-joined from-values) -> value.", "type": "object", "additionalProperties": { "type": "string" } },
513
+ "default": { "type": "string", "description": "literal, or $from[i] (the ONLY deref sigil). Used on lookup miss." }
514
+ }
515
+ },
516
+ "confirmDef": {
517
+ "type": "object", "additionalProperties": false, "required": ["step", "slot", "correctable"],
518
+ "properties": {
519
+ "step": { "$ref": "#/$defs/identifier", "description": "node id of the confirm state." },
520
+ "slot": { "$ref": "#/$defs/identifier", "description": "bool slot that records confirmation." },
521
+ "prompt": { "$ref": "#/$defs/promptDef", "description": "Compatibility copy of the matching correctable path prompt; when both are present they must be identical." },
522
+ "interactive": { "$ref": "#/$defs/choiceInteractiveDef", "description": "Compatibility copy of the matching correctable path interactive definition; when both are present they must be identical." },
523
+ "on_confirm": { "$ref": "#/$defs/identifier", "description": "step/anchor to go to on Yes (usually the terminal/requests anchor)." },
524
+ "correctable": { "description": "RAIL. The set of directly-correctable slots. The constrained decoder must return one exact identifier from this closed set; the compiler routes to its node and clears it + its transitive requires[]-dependents + reading derives. No clears[] list is hand-declared.", "type": "array", "items": { "$ref": "#/$defs/identifier" }, "minItems": 1, "uniqueItems": true }
525
+ }
526
+ },
527
+ "terminalDef": {
528
+ "type": "object", "additionalProperties": false, "required": ["step", "tool", "idempotent", "outcomes"],
529
+ "properties": {
530
+ "step": { "$ref": "#/$defs/identifier", "description": "node id (e.g. open_ticket)." },
531
+ "tool": { "$ref": "#/$defs/identifier", "description": "registered fulfillment tool (e.g. open_service_request)." },
532
+ "idempotent": { "type": "boolean", "description": "true => compiler computes sha256 key over inputs + replay-caches. Recipe stays in library." },
533
+ "input": { "description": "RAIL. param -> slot binding for the tool call.", "type": "array", "items": { "type": "object", "additionalProperties": false, "required": ["param", "slot"], "properties": { "param": { "$ref": "#/$defs/fieldIdentifier" }, "slot": { "$ref": "#/$defs/identifier" } } } },
534
+ "outputs": { "description": "RAIL. Mapping from destination state key to a canonical result.* tool-result path (e.g. protocol_id: result.protocol_id).", "type": "object", "propertyNames": { "$ref": "#/$defs/identifier" }, "additionalProperties": { "type": "string", "pattern": "^result\\.[A-Za-z][A-Za-z0-9_]*(?:\\.[A-Za-z][A-Za-z0-9_]*)*$" } },
535
+ "outcomes": {
536
+ "description": "RAIL. The outcome trichotomy. success: set+reset_next. retryable: preserve_state, re-enter. fatal: reset_next, END.",
537
+ "type": "object", "additionalProperties": false, "required": ["success", "retryable", "fatal"],
538
+ "properties": {
539
+ "success": { "type": "object", "additionalProperties": false, "properties": { "set": { "$ref": "#/$defs/stateLiteralMap" }, "reset_next": { "type": "boolean", "default": true } } },
540
+ "retryable": { "type": "object", "additionalProperties": false, "properties": { "preserve_state": { "type": "boolean", "default": true } } },
541
+ "fatal": { "type": "object", "additionalProperties": false, "properties": { "reset_next": { "type": "boolean", "default": true } } }
542
+ }
543
+ },
544
+ "empty_payload": { "description": "RAIL. Empty-payload reset semantics. never_saved: reset (treat as fresh). in_progress: ignore (don't wipe mid-flow).", "type": "object", "additionalProperties": false, "properties": { "never_saved": { "enum": ["reset", "ignore"], "default": "reset" }, "in_progress": { "enum": ["reset", "ignore"], "default": "ignore" } } }
545
+ }
546
+ },
547
+ "autoFlowDef": {
548
+ "type": "object", "additionalProperties": false, "required": ["meta_flow_ref", "send_when", "recovery"],
549
+ "properties": {
550
+ "meta_flow_ref": { "$ref": "#/$defs/fieldIdentifier", "description": "registered Meta Flow id (config.yaml whatsapp.flow.registry)." },
551
+ "send_when": { "$ref": "#/$defs/predicate", "description": "send the Flow envelope (pre-graph) iff true, e.g. not is_present(slots.streetlight_issue)." },
552
+ "prefill_from": { "type": "array", "uniqueItems": true, "items": { "$ref": "#/$defs/identifier" }, "description": "slots carried into flow_token (${data.X} prefill)." },
553
+ "resume_at": { "$ref": "#/$defs/identifier", "description": "path step id to resume at after nfm_reply submission." },
554
+ "on_submit_source": { "type": "string", "const": "whatsapp_flow" },
555
+ "alias_map": { "description": "RAIL but BOUNDED (W5). Flat fan-out for Meta-Flow field-shape mismatch ONLY. Outer key = Flow field name. Inner: either a direct slot mapping {slot:value}, OR value-keyed { <flow_value>: {slot:value,...} } for one-field->many-slots. No nested predicates, no transform DSL.", "type": "object", "propertyNames": { "$ref": "#/$defs/fieldIdentifier" }, "additionalProperties": { "$ref": "#/$defs/aliasMapping" } },
556
+ "recovery": { "$ref": "#/$defs/autoFlowRecoveryDef" }
557
+ }
558
+ },
559
+ "autoFlowRecoveryDef": {
560
+ "description": "Closed pending-state policy. The runtime accepts exact cancel, resend, fallback, and timeout events; the host schedules timeout from the emitted absolute deadline.",
561
+ "type": "object", "additionalProperties": false,
562
+ "required": ["fallback_at", "cancel", "timeout", "max_resends", "timeout_seconds"],
563
+ "properties": {
564
+ "fallback_at": { "$ref": "#/$defs/identifier", "description": "Native path step used by the fallback event and fallback policy actions." },
565
+ "cancel": { "enum": ["END", "fallback"], "description": "Deterministic action for a cancel event." },
566
+ "timeout": { "enum": ["END", "fallback"], "description": "Deterministic action when the deadline expires or a timeout event arrives." },
567
+ "max_resends": { "type": "integer", "minimum": 0 },
568
+ "timeout_seconds": { "type": "integer", "exclusiveMinimum": 0 }
569
+ }
570
+ },
571
+ "aliasLiteral": {
572
+ "description": "A JSON scalar or $value, copied without evaluation beyond the exact $value placeholder.",
573
+ "type": ["string", "number", "boolean", "null"]
574
+ },
575
+ "directAliasMapping": {
576
+ "type": "object", "minProperties": 1,
577
+ "propertyNames": { "$ref": "#/$defs/identifier" },
578
+ "additionalProperties": { "$ref": "#/$defs/aliasLiteral" }
579
+ },
580
+ "aliasMapping": {
581
+ "oneOf": [
582
+ { "$ref": "#/$defs/directAliasMapping" },
583
+ {
584
+ "type": "object", "minProperties": 1,
585
+ "additionalProperties": { "$ref": "#/$defs/directAliasMapping" }
586
+ }
587
+ ]
588
+ },
589
+ "transition": {
590
+ "description": "A routing target with optional set. Used by await_external timeout/recovery branches.",
591
+ "type": "object", "additionalProperties": false, "required": ["goto"],
592
+ "properties": { "goto": { "oneOf": [{ "$ref": "#/$defs/identifier" }, { "const": "END" }], "description": "step id or 'END'." }, "set": { "$ref": "#/$defs/stateLiteralMap" } }
593
+ },
594
+ "resumeContractDef": {
595
+ "description": "Closed contract for host-delivered resume tokens. The embedded schema is validated as Draft 2020-12, may use only acyclic local references, and must close its object boundary.",
596
+ "type": "object", "additionalProperties": false,
597
+ "required": ["version", "schema", "correlation", "duplicate", "late"],
598
+ "properties": {
599
+ "version": { "type": "string", "pattern": "^[1-9][0-9]*(?:\\.[0-9]+){0,2}$" },
600
+ "schema": { "type": "object" },
601
+ "correlation": { "type": "string", "pattern": "^\\$token(?:\\.[A-Za-z][A-Za-z0-9_]*)+$" },
602
+ "duplicate": { "enum": ["ignore", "reject"] },
603
+ "late": { "enum": ["ignore", "reject"] }
604
+ }
605
+ },
606
+ "bindingLiteral": {
607
+ "description": "A bounded mapping value: a JSON scalar literal or a compiler-checked $token.* / $result.* reference.",
608
+ "type": ["string", "number", "boolean", "null"]
609
+ },
610
+ "stateBindingMap": {
611
+ "type": "object",
612
+ "propertyNames": { "$ref": "#/$defs/identifier" },
613
+ "additionalProperties": { "$ref": "#/$defs/bindingLiteral" }
614
+ },
615
+ "toolInputBindingMap": {
616
+ "type": "object",
617
+ "propertyNames": { "$ref": "#/$defs/fieldIdentifier" },
618
+ "additionalProperties": { "$ref": "#/$defs/bindingLiteral" }
619
+ },
620
+ "stateLiteralMap": {
621
+ "type": "object",
622
+ "propertyNames": { "$ref": "#/$defs/identifier" },
623
+ "additionalProperties": true
624
+ },
625
+ "enrichmentDef": {
626
+ "type": "object", "additionalProperties": false, "required": ["tool"],
627
+ "properties": {
628
+ "tool": { "$ref": "#/$defs/identifier" },
629
+ "optional": { "type": "boolean", "default": false, "description": "When true, a logged tool failure does not discard successfully resolved token mappings." },
630
+ "input": { "$ref": "#/$defs/toolInputBindingMap", "description": "Tool parameter -> literal or $token.path binding." },
631
+ "set": { "$ref": "#/$defs/stateBindingMap", "description": "State key -> literal or $result.path binding." }
632
+ }
633
+ }
634
+ }
635
+ }