fluidgrids-workflows 2026.1030.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. fluidgrids_workflows-2026.1030.1/.gitignore +30 -0
  2. fluidgrids_workflows-2026.1030.1/CHANGELOG.md +144 -0
  3. fluidgrids_workflows-2026.1030.1/LICENSE +9 -0
  4. fluidgrids_workflows-2026.1030.1/PKG-INFO +356 -0
  5. fluidgrids_workflows-2026.1030.1/README.md +318 -0
  6. fluidgrids_workflows-2026.1030.1/pyproject.toml +124 -0
  7. fluidgrids_workflows-2026.1030.1/src/fluidgrids_workflows/__init__.py +175 -0
  8. fluidgrids_workflows-2026.1030.1/src/fluidgrids_workflows/__main__.py +7 -0
  9. fluidgrids_workflows-2026.1030.1/src/fluidgrids_workflows/_json.py +269 -0
  10. fluidgrids_workflows-2026.1030.1/src/fluidgrids_workflows/_project.py +115 -0
  11. fluidgrids_workflows-2026.1030.1/src/fluidgrids_workflows/_version.py +24 -0
  12. fluidgrids_workflows-2026.1030.1/src/fluidgrids_workflows/builder/__init__.py +1 -0
  13. fluidgrids_workflows-2026.1030.1/src/fluidgrids_workflows/builder/assemble.py +257 -0
  14. fluidgrids_workflows-2026.1030.1/src/fluidgrids_workflows/builder/descriptors.py +182 -0
  15. fluidgrids_workflows-2026.1030.1/src/fluidgrids_workflows/builder/refs.py +447 -0
  16. fluidgrids_workflows-2026.1030.1/src/fluidgrids_workflows/builder/workflow.py +1191 -0
  17. fluidgrids_workflows-2026.1030.1/src/fluidgrids_workflows/cli.py +258 -0
  18. fluidgrids_workflows-2026.1030.1/src/fluidgrids_workflows/history/__init__.py +264 -0
  19. fluidgrids_workflows-2026.1030.1/src/fluidgrids_workflows/history/allowlist.py +119 -0
  20. fluidgrids_workflows-2026.1030.1/src/fluidgrids_workflows/history/defaults.py +31 -0
  21. fluidgrids_workflows-2026.1030.1/src/fluidgrids_workflows/history/file.py +73 -0
  22. fluidgrids_workflows-2026.1030.1/src/fluidgrids_workflows/history/gate.py +208 -0
  23. fluidgrids_workflows-2026.1030.1/src/fluidgrids_workflows/history/schema/history-file.schema.json +2825 -0
  24. fluidgrids_workflows-2026.1030.1/src/fluidgrids_workflows/history/schema/run-event.schema.json +2636 -0
  25. fluidgrids_workflows-2026.1030.1/src/fluidgrids_workflows/history/schema/workflow-command.schema.json +1272 -0
  26. fluidgrids_workflows-2026.1030.1/src/fluidgrids_workflows/history/schema/workflow-task.schema.json +3948 -0
  27. fluidgrids_workflows-2026.1030.1/src/fluidgrids_workflows/history/schema.py +73 -0
  28. fluidgrids_workflows-2026.1030.1/src/fluidgrids_workflows/history/types.py +805 -0
  29. fluidgrids_workflows-2026.1030.1/src/fluidgrids_workflows/history/validate.py +276 -0
  30. fluidgrids_workflows-2026.1030.1/src/fluidgrids_workflows/ir/__init__.py +132 -0
  31. fluidgrids_workflows-2026.1030.1/src/fluidgrids_workflows/ir/_cron.py +975 -0
  32. fluidgrids_workflows-2026.1030.1/src/fluidgrids_workflows/ir/canonical.py +206 -0
  33. fluidgrids_workflows-2026.1030.1/src/fluidgrids_workflows/ir/diff.py +122 -0
  34. fluidgrids_workflows-2026.1030.1/src/fluidgrids_workflows/ir/duration.py +73 -0
  35. fluidgrids_workflows-2026.1030.1/src/fluidgrids_workflows/ir/executor_view.py +132 -0
  36. fluidgrids_workflows-2026.1030.1/src/fluidgrids_workflows/ir/expressions.py +243 -0
  37. fluidgrids_workflows-2026.1030.1/src/fluidgrids_workflows/ir/hashing.py +71 -0
  38. fluidgrids_workflows-2026.1030.1/src/fluidgrids_workflows/ir/ir-v2.schema.json +716 -0
  39. fluidgrids_workflows-2026.1030.1/src/fluidgrids_workflows/ir/layout.py +73 -0
  40. fluidgrids_workflows-2026.1030.1/src/fluidgrids_workflows/ir/node_types.py +100 -0
  41. fluidgrids_workflows-2026.1030.1/src/fluidgrids_workflows/ir/normalize.py +310 -0
  42. fluidgrids_workflows-2026.1030.1/src/fluidgrids_workflows/ir/schedule.py +247 -0
  43. fluidgrids_workflows-2026.1030.1/src/fluidgrids_workflows/ir/schema.py +45 -0
  44. fluidgrids_workflows-2026.1030.1/src/fluidgrids_workflows/ir/validate.py +895 -0
  45. fluidgrids_workflows-2026.1030.1/src/fluidgrids_workflows/lower.py +454 -0
  46. fluidgrids_workflows-2026.1030.1/src/fluidgrids_workflows/project/__init__.py +42 -0
  47. fluidgrids_workflows-2026.1030.1/src/fluidgrids_workflows/py.typed +0 -0
@@ -0,0 +1,30 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .venv/
6
+ build/
7
+ dist/
8
+
9
+ # Tool caches and reports
10
+ .pytest_cache/
11
+ .mypy_cache/
12
+ .ruff_cache/
13
+ .coverage
14
+ .coverage.*
15
+ coverage.xml
16
+ htmlcov/
17
+
18
+ # Node (the TypeScript reference package installed for scripts/sync_fixtures.py)
19
+ node_modules/
20
+
21
+ # IDE and OS
22
+ .vscode/
23
+ .idea/
24
+ *.swp
25
+ .DS_Store
26
+
27
+ # Environment and local agent state
28
+ .env
29
+ .env.*
30
+ .claude/
@@ -0,0 +1,144 @@
1
+ # Changelog
2
+
3
+ All notable changes to `fluidgrids-workflows` are recorded here. Versions are
4
+ CalVer (`YYYY.MDD.PATCH`); the release workflow stamps the version, so
5
+ `pyproject.toml` is not bumped by hand.
6
+
7
+ ## Unreleased (R2 vendoring)
8
+
9
+ The Python side of SDK release R2 (BOFF-9905, work package B19-P2). Python
10
+ tier 2 is still Phase C, so no tier-2 options are added; the Python SDK reads
11
+ and checks R2 histories.
12
+
13
+ - The fixtures and the five JSON Schemas are vendored from
14
+ `fluidgrids-workflows-sdk-node` `8274143` (SDK R2: the C-GATE and C-SCHEMA
15
+ freeze plus the R2 runtime). `fixtures/histories/` now holds 78 histories:
16
+ the 53 R1 histories, unchanged, and 25 new ones (the 21 R2 conformance
17
+ histories plus `local-activity-cancelled-in-scope`,
18
+ `local-activity-abandoned-at-completion`,
19
+ `continue-as-new-r1-retry-options` and
20
+ `continue-as-new-r1-long-task-queue`).
21
+ - Histories that Phase B (R1) runs stored keep loading: the vendored schemas
22
+ leave `WORKFLOW_CONTINUED_AS_NEW.options` a plain object (an R1 run could
23
+ store a fractional `retryDelay`, a `backoffMultiplier` below 1 or a task
24
+ queue over 256 characters there), and the activity-shaped `CONTINUE_AS_NEW`
25
+ retry has the R1 number semantics.
26
+ - The command feature gate (C-GATE) is ported: `CommandFeature`,
27
+ `COMMAND_FEATURES`, `PHASE_B_CORE_FEATURES`, `PHASE_B_PARITY_FEATURES`,
28
+ `CORE_CHILD_OPTIONS`, `CORE_CAN_OPTIONS`, `command_features`,
29
+ `continue_as_new_retry_shape`, `CommandRelief`, `is_relief_command` and
30
+ `unsupported_feature_message`. `validate_commands` and
31
+ `is_workflow_command_enabled` take a feature set (`CommandGate`), and
32
+ `validate_commands` takes the lease's `relief`. The release levels `"core"`
33
+ and `"parity"` and `is_command_enabled` are deprecated: they keep their R1
34
+ meaning and are not the service's Phase B gate. `scripts/ts_reference.mjs`
35
+ records the gate's verdicts for every command batch, 23 new R2 batches
36
+ among them.
37
+ - `durable_phase_issues` (and `validate(..., refuse_phase_c=True)`) refuses
38
+ every `code.features` entry that is not a Phase B runtime feature: the Phase
39
+ C ones and unknown values (`PHASE_B_CODE_FEATURES`,
40
+ `PHASE_C_CODE_FEATURES`). The IR v2 schema gains
41
+ `config.retry`, `code.features` and `code.exports.workflows.<fn>.config`.
42
+ - `fluidgrids_workflows.history` gains the C-SCHEMA fields:
43
+ `WorkflowInfoSnapshot` adds `workflowId`, `runAttempt`, `memo`,
44
+ `versioningBehavior` and `retry` (and `timeouts` is typed);
45
+ `WorkflowIdTarget` adds `firstRunId`; `RecordLocalActivityCommand` adds
46
+ `nodeType`, `operation` and `summary`; `WorkflowTaskResult` adds
47
+ `unfinishedHandlers` and types `localActivityRequests`.
48
+ - New TypedDicts: `RunRetryPolicy`, `WorkflowTimeouts`, `WorkflowTaskRequest`,
49
+ `BuiltInQuery`, `LocalActivityRequest` (with `attempt`, `backoffMs` and
50
+ `summary`), `LocalActivityResult` and `UnfinishedHandler`. New constants:
51
+ `RUNTIME_RELEASE`, `WORKFLOW_ID_REUSE_POLICIES`,
52
+ `WORKFLOW_ID_CONFLICT_POLICIES`, `VERSIONING_BEHAVIORS`,
53
+ `WORKFLOW_TIMEOUT_TYPES` and `LOCAL_ACTIVITY_MAX_TIMEOUT_MS`, checked
54
+ against the TypeScript values in `fixtures/ts-vectors.json`.
55
+ - New tests compare every history TypedDict, command TypedDicts included,
56
+ with its definition in the vendored schemas: the keys, the required keys,
57
+ and the `Literal` values of every `enum` or `const` property (or the
58
+ constant tuple a `str`-typed enum is built from), so a schema change that
59
+ the Python types miss fails the suite. `WorkflowTaskRequest.protocol` and
60
+ `WorkflowTaskResult.protocol` are typed `Literal[1]`. Integer properties stay
61
+ `int | float`, as in R1: JSON Schema counts `1.0` as an integer.
62
+ - The TypeScript reference outputs come from the published R2,
63
+ `@fluidgrids/workflows@2026.1029.1` (npm `gitHead` `8274143`), pinned in
64
+ `scripts/ts-reference`. They differ from the 2026.1028.2 outputs only in
65
+ `authoring.generatorVersion` and in the history vectors for the new files
66
+ and constants.
67
+ The release workflow now runs the same `references` check and publishes
68
+ only when it passes, so a release cannot ship fixtures no published
69
+ TypeScript SDK backs.
70
+
71
+ ## Unreleased (2026.1028.x)
72
+
73
+ Phase B of the code-first spec set (BOFF-9905, work package B2): the Python
74
+ SDK's part of managed durable functions. It pairs with
75
+ `@fluidgrids/workflows@2026.1028.2`.
76
+
77
+ - `wf.code_workflow(export_name, ...)` adds an `internal:code-workflow` node
78
+ with `input`, `task_queue`, `wait_for_completion`, `timeout_seconds`,
79
+ `fail_on_error` and `parent_close_policy`, lowering byte for byte like
80
+ `wf.codeWorkflow`. Options left out are not written; the service applies the
81
+ DB13 parent-close default.
82
+ - `wf.wait_for_signal` takes `consume` (`first` or `all`) and `until`
83
+ (`limitWaitUntil`, an ISO 8601 string or a timezone-aware `datetime`).
84
+ - `validate` ports the B1 rules: the warnings `codeWorkflow.closePolicyEndsChild`
85
+ and `codeWorkflow.timeoutIgnored`, the error `waitForSignal.invalidDeadline`
86
+ (with a port of `Date.parse` for ISO strings), and the schema's new
87
+ wait-for-signal fields.
88
+ - `durable_phase_issues` and `validate(..., refuse_phase_c=True)` report the
89
+ features that arrive in Phase C (`durable.phaseC`): Python bundles,
90
+ self-hosted workers, user activities and task queues other than `managed`.
91
+ `lower_project` returns them as `LowerResult.issues`, and the `lower` command
92
+ prints them under `issues`; `validate --refuse-phase-c` is the command-line
93
+ form.
94
+ - `fluidgrids_workflows.history`: the durable-function contract (enums,
95
+ TypedDicts, the four JSON Schemas vendored from the TypeScript SDK, command
96
+ and history validation, `load_history`, the activity allow-list and the
97
+ DB13 default).
98
+ - The fixtures are vendored from `fluidgrids-workflows-sdk-node` `9a02b2c` (tag 2026.1028.2),
99
+ including the 53 durable-function histories in `fixtures/histories/`; the
100
+ TypeScript reference outputs come from `@fluidgrids/workflows@2026.1028.2`.
101
+ - `conformance/hybrid-code-workflow/` is a Python-owned conformance fixture
102
+ with its TypeScript twin; `scripts/ts_reference.mjs` lowers the twin.
103
+
104
+ ## 2026.1027.x
105
+
106
+ The first release: the Python half of Phase A of the code-first workflows spec
107
+ set (BOFF-9904, epic BOFF-9902).
108
+
109
+ - The tier-1 graph builder (`define_workflow`, `wf.trigger`, `wf.step` and a
110
+ helper for every internal node type, typed references and predicates,
111
+ `expr`, `edge`, `wf.connect`, `wf.edge_order_is`, sticky notes, `extra`),
112
+ lowering byte for byte to the same IR v2 as `@fluidgrids/workflows` on every
113
+ shared fixture.
114
+ - `to_ir` / `assemble_recording` with connection resolution, the layout
115
+ sidecar, the automatic layout and pinned data; `lower_project` for a project
116
+ directory, with a determinism check that imports the project twice.
117
+ - Ports of the IR tools: `normalize_definition`, `validate` (with `strict` and
118
+ `is_task_queue_known`), `ir_hash`, RFC 8785 canonical JSON, `stable_stringify`,
119
+ `diff_definitions`, `executor_view`, `auto_layout`, durations, schedule checks
120
+ and the `{{ … }}` reference parser; the IR v2 JSON Schema.
121
+ - `fluidgrids_workflows.project` loads the project's generated node clients
122
+ (`.fluidgrids/nodes.py`), and `op` / `trigger` / `Json` / `FileRef` are the
123
+ names those clients import.
124
+ - The `fluidgrids-workflows` command line: `lower`, `validate`, `hash`,
125
+ `normalize`.
126
+ - The shared fixtures and TypeScript reference outputs of
127
+ `@fluidgrids/workflows@2026.1027.1`, vendored with checksums.
128
+ - Cron schedules are checked by a port of `cron-parser` (`ir/_cron.py`)
129
+ instead of `croniter`, so `validate` gives the same verdict and fire times as
130
+ the TypeScript SDK, including the empty expression, `?`, `L`, `#`, `W`,
131
+ reversed ranges and lower-case or offset time zones. `croniter` is no longer
132
+ a dependency.
133
+ - `lower` keeps standard output for its JSON result: what the project prints
134
+ while it is imported (`print`, logging, C code writing to descriptor 1) goes
135
+ to standard error.
136
+ - The determinism check also re-imports project modules imported by absolute
137
+ name (`from workflows.helpers import X`), and lowering unloads them.
138
+ - A workflow module that calls `sys.exit` fails lowering with reason `import`.
139
+ - The command line reports every error as one `fluidgrids-workflows:` line,
140
+ reads files that are not valid UTF-8 with replacement characters as Node
141
+ does, and reads integers beyond the double range as `Infinity`, as
142
+ `JSON.parse` does.
143
+ - `validate` leaves `nodeId` out of a per-node issue when the node id is the
144
+ empty string, as the TypeScript SDK does.
@@ -0,0 +1,9 @@
1
+ Copyright (c) 2025 Algoshred Technologies Private Limited.
2
+
3
+ All rights reserved.
4
+
5
+ This software is proprietary and confidential. Unauthorized copying, modification, distribution, or use of this software, via any medium, is strictly prohibited without the express written permission of the copyright holders.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8
+
9
+ For licensing inquiries, please contact: legal@algoshred.com
@@ -0,0 +1,356 @@
1
+ Metadata-Version: 2.5
2
+ Name: fluidgrids-workflows
3
+ Version: 2026.1030.1
4
+ Summary: Author FluidGrids workflows as code in Python: the tier-1 graph builder, byte-identical lowering to the workflow IR v2, and the IR tools (normalise, validate, hash).
5
+ Project-URL: Homepage, https://fluidgrids.ai
6
+ Project-URL: Repository, https://github.com/Algoshred/fluidgrids-workflows-sdk-python
7
+ Project-URL: Issues, https://github.com/Algoshred/fluidgrids-workflows-sdk-python/issues
8
+ Project-URL: Changelog, https://github.com/Algoshred/fluidgrids-workflows-sdk-python/blob/main/CHANGELOG.md
9
+ Author-email: FluidGrids <info@fluidgrids.ai>
10
+ Maintainer-email: FluidGrids <info@fluidgrids.ai>
11
+ License: Copyright (c) 2025 Algoshred Technologies Private Limited.
12
+
13
+ All rights reserved.
14
+
15
+ This software is proprietary and confidential. Unauthorized copying, modification, distribution, or use of this software, via any medium, is strictly prohibited without the express written permission of the copyright holders.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
18
+
19
+ For licensing inquiries, please contact: legal@algoshred.com
20
+ License-File: LICENSE
21
+ Keywords: automation,fluidgrids,sdk,workflow,workflow-as-code
22
+ Classifier: Development Status :: 3 - Alpha
23
+ Classifier: Intended Audience :: Developers
24
+ Classifier: License :: Other/Proprietary License
25
+ Classifier: Operating System :: OS Independent
26
+ Classifier: Programming Language :: Python :: 3
27
+ Classifier: Programming Language :: Python :: 3 :: Only
28
+ Classifier: Programming Language :: Python :: 3.11
29
+ Classifier: Programming Language :: Python :: 3.12
30
+ Classifier: Programming Language :: Python :: 3.13
31
+ Classifier: Programming Language :: Python :: 3.14
32
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
33
+ Classifier: Typing :: Typed
34
+ Requires-Python: >=3.11
35
+ Requires-Dist: jsonschema<5,>=4.23
36
+ Requires-Dist: tzdata>=2025.2; sys_platform == 'win32'
37
+ Description-Content-Type: text/markdown
38
+
39
+ # fluidgrids-workflows
40
+
41
+ Author [FluidGrids](https://fluidgrids.ai) workflows as code in Python. This
42
+ package is the Python half of Phase A of the code-first workflows spec set
43
+ (BOFF-9902 / BOFF-9904, `fluidgrids-specs/product/specs/code-first/`, spec 03):
44
+
45
+ - the tier-1 graph builder: Python that constructs a canvas workflow;
46
+ - lowering a Python project to the workflow IR, schema version 2, byte for
47
+ byte the same as `@fluidgrids/workflows` lowers the TypeScript twin of the
48
+ same graph;
49
+ - ports of the IR tools: `normalize_definition`, `validate`, `ir_hash`,
50
+ canonical JSON (RFC 8785), the structural diff, the executor view, the
51
+ automatic layout, durations and schedule checks;
52
+ - loading the typed node clients that `fluidgrids codegen --lang python`
53
+ generates (`.fluidgrids/nodes.py`);
54
+ - from Phase B (BOFF-9905): `wf.code_workflow`, which embeds a TypeScript
55
+ durable function in a graph, and `fluidgrids_workflows.history`, the
56
+ durable-function contract (event and command enums, the command and history
57
+ JSON Schemas, command and history validation, `load_history`).
58
+
59
+ Python durable (tier-2) workflow functions and the Python worker are Phase C
60
+ (BOFF-9906) and are not in this package yet; `lower` and `validate
61
+ --refuse-phase-c` report what needs them as `durable.phaseC`. Lifting (IR to
62
+ Python source) lives in the TypeScript package, which the service and the CLI
63
+ use for `fluidgrids pull --format py`.
64
+
65
+ ## Install
66
+
67
+ ```bash
68
+ uv add fluidgrids-workflows # or: pip install fluidgrids-workflows
69
+ ```
70
+
71
+ Python 3.11 or later. The floor matches `fluidgrids-node-sdk` (spec 03 §1) and
72
+ the generated clients, which use `typing.Required`.
73
+
74
+ ## Tier 1: the graph builder
75
+
76
+ ```python
77
+ from fluidgrids_workflows import define_workflow, expr, triggers
78
+ from fluidgrids_workflows.project import nodes as n
79
+
80
+
81
+ @define_workflow(key="lead-enrichment", name="Lead enrichment")
82
+ def lead_enrichment(wf):
83
+ inbound = wf.trigger(triggers.webhook, id="inbound", args={"path": "leads"})
84
+ enrich = wf.http(
85
+ id="enrich",
86
+ after=inbound,
87
+ connection="enrichment-api",
88
+ args={
89
+ "method": "GET",
90
+ "url": "https://person.clearbit.com/v2/combined/find",
91
+ "queryParams": {"email": wf.json.body.email},
92
+ },
93
+ retry={"maxRetries": 3, "retryDelay": 2000},
94
+ )
95
+ is_enterprise = wf.if_(
96
+ id="is-enterprise",
97
+ after=enrich,
98
+ condition=enrich.output.body.company.metrics.employees >= 500,
99
+ )
100
+ wf.step(
101
+ n.slack.send_message,
102
+ id="notify-sales",
103
+ after=is_enterprise.true,
104
+ connection="slack-ops",
105
+ args={
106
+ "channelId": "C0SALES",
107
+ "text": expr("New enterprise lead: ", enrich.output.body.person.name.fullName),
108
+ },
109
+ )
110
+ ```
111
+
112
+ The decorated function runs once, immediately, and records a graph;
113
+ `to_ir(workflow, connections=..., layout=...)` turns the recording into IR v2.
114
+ Lowering is a pure function of the builder calls, so the same source always
115
+ gives the same IR.
116
+
117
+ - Builder methods and keyword options are snake_case (`wf.if_`,
118
+ `wf.wait_for_signal`, `on_error`, `execution_settings`). Dictionary values
119
+ (`args`, `retry`, `config`, `extra`, …) keep the IR's own keys
120
+ (`maxRetries`, `channelId`) and lower byte for byte.
121
+ - Every node needs an `id` (`define_workflow(..., auto_ids=True)` derives them
122
+ from labels for quick scripts).
123
+ - References lower to the expression language the workers evaluate:
124
+ `enrich.output.a.b` becomes `{{ $node["enrich"].item.a.b }}` and `wf.json.x`
125
+ becomes `{{ $json.x }}`. Use `ref["key"]` for keys that are not identifiers,
126
+ are Python keywords, start with `_` or are reference method names, and
127
+ `ref[0]` for indices.
128
+ - Comparisons (`>=`, `==`, …, `contains`, `is_empty`, `matches`, the generic
129
+ `is_(operator, value)`) and `and_()` / `or_()` become the condition tree
130
+ `internal:if` evaluates. `and`, `or`, `not` and `in` raise, because they would
131
+ be evaluated by Python at definition time.
132
+ - `expr("text ", ref, " more")` concatenates text and references.
133
+ - Leaving an option out writes the default; `False` for `operation`, `version`
134
+ and edge `id` / `type` writes no such field; `None` is JSON `null`.
135
+ - `edge(source, id=..., type=..., target_handle=..., label=..., data=...,
136
+ mapping=..., extra=...)`, `wf.connect`, `wf.edge_order_is` and the `extra`
137
+ option on every node keep any stored definition expressible exactly.
138
+ - An edge without an explicit `id` gets
139
+ `e-<source>-<target>[-<sourceHandle>][-<targetHandle>]`; when that id is taken
140
+ the later edges get `-2`, `-3`, … in declaration order.
141
+ - `connection="name"` resolves to a connection id when the workflow is pushed;
142
+ an unknown name raises `UnresolvedConnectionsError`.
143
+ - Positions come from the layout sidecar `workflows/<key>.layout.json`, or the
144
+ automatic layout the AI draft generator uses.
145
+
146
+ The complete surface is pinned by the shared fixtures; `fixtures/README.md`
147
+ lists it.
148
+
149
+ ### Durable functions in a graph
150
+
151
+ `wf.code_workflow("<exportName>", ...)` adds an `internal:code-workflow` node
152
+ that runs a `workflow()` function of the definition's code bundle as a durable
153
+ child run. In Phase B the bundle is a TypeScript one, given with
154
+ `define_workflow(code=...)`:
155
+
156
+ ```python
157
+ from datetime import UTC, datetime
158
+
159
+ process = wf.code_workflow(
160
+ "processOrder",
161
+ id="process-order",
162
+ after=received,
163
+ input={"order": wf.json.body}, # inputMapping
164
+ timeout_seconds=900, # 0 or absent: no node-level timeout
165
+ fail_on_error=False,
166
+ )
167
+ shipped = wf.wait_for_signal(
168
+ id="await-shipment",
169
+ after=process,
170
+ name="shipped",
171
+ consume="all", # or "first" (the default)
172
+ until=datetime(2026, 12, 1, tzinfo=UTC), # limitWaitUntil; or limit="2d"
173
+ )
174
+ ```
175
+
176
+ As with every builder option, an option left out writes nothing and the
177
+ service applies the default: `task_queue` `managed`, `wait_for_completion`
178
+ `True`, and the parent-close policy of decision DB13, `TERMINATE` when the node
179
+ waits for the child and `ABANDON` when it does not
180
+ (`fluidgrids_workflows.history.code_workflow_parent_close_policy`).
181
+ `validate` warns about a fire-and-forget node with `TERMINATE` or
182
+ `REQUEST_CANCEL` (`codeWorkflow.closePolicyEndsChild`) or a timeout
183
+ (`codeWorkflow.timeoutIgnored`). `until` takes an ISO 8601 string or a
184
+ timezone-aware `datetime`.
185
+
186
+ ### What arrives in Phase C
187
+
188
+ `durable_phase_issues(definition)` (in `fluidgrids_workflows.ir`) lists, with
189
+ code `durable.phaseC`, what a definition needs that Phase B does not run: a
190
+ Python code bundle, `determinism: "worker"`, `code.exports.activities` (user
191
+ activities), task queues other than `managed`, and every `code.features` entry
192
+ that is not a Phase B runtime feature: the Phase C ones (`userActivity`,
193
+ `updates`, `queries`, `searchAttributes`, `dynamic`, `interceptors`) and any
194
+ value the SDK does not know, so the check fails closed
195
+ (`PHASE_B_CODE_FEATURES`, `PHASE_C_CODE_FEATURES`). `lower_project` returns them
196
+ as `LowerResult.issues`, `fluidgrids-workflows lower` prints them under
197
+ `issues`, and `validate(..., refuse_phase_c=True)` (`validate
198
+ --refuse-phase-c`) makes them errors. Without that option `validate` gives the
199
+ TypeScript SDK's verdict.
200
+
201
+ ## Projects and generated node clients
202
+
203
+ A project is a directory with workflow modules in `workflows/*.py`, optional
204
+ layout sidecars (`workflows/<key>.layout.json`), TEST-only pinned data
205
+ (`workflows/<key>.pinned.json`) and the generated clients in
206
+ `.fluidgrids/nodes.py`. Workflow modules import the clients with
207
+ `from fluidgrids_workflows.project import nodes as n`, which resolves to the
208
+ project being lowered, then `FLUIDGRIDS_PROJECT_DIR`, then the nearest
209
+ `.fluidgrids/nodes.py` at or above the working directory.
210
+
211
+ ```python
212
+ from fluidgrids_workflows import lower_project
213
+
214
+ result = lower_project(".", connections={"slack-ops": {"connectionId": "c1", "adapter": "slack"}})
215
+ for workflow in result.workflows:
216
+ print(workflow.key, workflow.definition)
217
+ ```
218
+
219
+ `lower_project` imports the project's modules, so it is for code the caller
220
+ trusts (the CLI lowering a developer's own project; services never run Python
221
+ workflow code, spec decision D1). It imports the project twice and refuses a
222
+ workflow whose graph changes between the two (`LowerError(reason="determinism")`),
223
+ which catches definitions that read the clock or a random source.
224
+
225
+ ## Command line
226
+
227
+ ```bash
228
+ fluidgrids-workflows lower [PROJECT] [--connections FILE] [--entry workflows/x.py]
229
+ fluidgrids-workflows validate FILE [--strict] [--allow-empty] [--refuse-phase-c]
230
+ fluidgrids-workflows hash FILE
231
+ fluidgrids-workflows normalize FILE
232
+ ```
233
+
234
+ Every command prints JSON; `FILE` may be `-` for standard input. The FluidGrids
235
+ CLI calls `lower` for Python projects; its result carries `issues` only when a
236
+ workflow needs a Phase C feature.
237
+
238
+ ## The IR
239
+
240
+ `fluidgrids_workflows.ir` ports `@fluidgrids/workflows/ir`:
241
+
242
+ - `normalize_definition` rewrites the builder's legacy mirrors into the
243
+ canonical node fields following the executor's reading order; it is pure and
244
+ idempotent.
245
+ - `ir_hash` is `sha256:` + the SHA-256 of the RFC 8785 canonical JSON of the
246
+ normalised definition without `authoring`, so both SDKs and the canvas hash a
247
+ graph equally.
248
+ - `validate(definition, strict=..., is_task_queue_known=...)` applies the
249
+ service's structural rules and the version-2 rules of spec 01 §9. Issue codes
250
+ match the TypeScript validator; messages are English and may differ in
251
+ wording.
252
+ - `canonical_json`, `stable_stringify`, `diff_definitions`, `executor_view`,
253
+ `auto_layout`, `duration_ms`, `check_schedule_spec` and the `{{ … }}`
254
+ reference parser are exact ports.
255
+ - `ir_v2_schema()` is the IR v2 JSON Schema, vendored byte for byte from the
256
+ TypeScript package.
257
+
258
+ ## The durable-function contract
259
+
260
+ `fluidgrids_workflows.history` is the Python side of
261
+ `@fluidgrids/workflows/history`, the contract the service, the workers and both
262
+ SDKs share:
263
+
264
+ - `EVENT_TYPES`, `COMMAND_TYPES`, `TASK_FAILURE_CAUSES`, the Phase B core,
265
+ parity and Phase C command sets, and the event classes, in the TypeScript
266
+ order;
267
+ - TypedDicts for events (`RunEventRecord`), every command, failures, retry
268
+ policies, task requests and results, local-activity requests and results,
269
+ run targets and history files; `tests/test_history.py` checks each one
270
+ against the vendored schemas;
271
+ - the runtime release the contract describes (`RUNTIME_RELEASE`, 2 from SDK
272
+ R2) and the R2 enums: workflow-id reuse and conflict policies, versioning
273
+ behaviours, workflow timeout types and `LOCAL_ACTIVITY_MAX_TIMEOUT_MS`;
274
+ - `history_schema(name)` for the four vendored JSON Schemas (`run-event`,
275
+ `workflow-command`, `workflow-task`, `history-file`);
276
+ - `validate_command`, `validate_commands` (shape, the gate, unique ids,
277
+ terminal command last, the activity allow-list), `validate_history` and
278
+ `validate_history_file`, with the TypeScript verdicts and failure causes;
279
+ - the command feature gate (C-GATE) the service applies: `command_features`,
280
+ `PHASE_B_CORE_FEATURES`, `PHASE_B_PARITY_FEATURES` (what
281
+ `fluidgrids.durable-parity` adds), `is_relief_command` and `CommandRelief`.
282
+ Pass a feature set as `validate_commands(..., enabled=PHASE_B_CORE_FEATURES)`
283
+ for the service's Phase B verdict. The default levels `"core"` and
284
+ `"parity"` (and `is_command_enabled`) are deprecated: they keep their R1
285
+ meaning, decide by command type only and are not the service's gate (`core`
286
+ accepts the R2 child options and workflow-id targets the service refuses
287
+ without the parity flag, and refuses `CONTINUE_AS_NEW`, which it accepts);
288
+ - `load_history(path)` / `parse_history_file(text)`, which raise
289
+ `HistoryFileError` for a file that does not match the schema;
290
+ - `is_activity_node_type_allowed` (the activity allow-list) and
291
+ `code_workflow_parent_close_policy` (DB13).
292
+
293
+ ```python
294
+ from fluidgrids_workflows.history import load_history
295
+
296
+ history = load_history("fixtures/histories/signal-wait.history.json")
297
+ print([event["type"] for event in history["events"]])
298
+ ```
299
+
300
+ Python does not replay histories in Phase B; the replay runner is the
301
+ TypeScript SDK's.
302
+
303
+ ## Conformance with the TypeScript SDK
304
+
305
+ `fixtures/` is vendored from `fluidgrids-workflows-sdk-node` by
306
+ `scripts/sync_fixtures.py`, together with reference outputs the published
307
+ `@fluidgrids/workflows` computes (`<fixture>/lowered.ts.json` and
308
+ `fixtures/ts-vectors.json`). The test suite:
309
+
310
+ - lowers every fixture's `workflows/<key_snake>.py` and requires the canonical
311
+ JSON to equal the TypeScript lowering of `workflows/<key>.ts` (apart from the
312
+ provenance block) and the `irHash` to equal `fixture.json`;
313
+ - checks normalisation, hashing, the executor view, validation codes, canonical
314
+ JSON, SHA-256, durations, schedules, reference parsing, diffs and layouts
315
+ against the TypeScript outputs;
316
+ - lowers the Python-owned fixtures in `conformance/` (features that are only
317
+ sugar in a builder, such as `wf.code_workflow`, whose TypeScript twins the
318
+ lifted fixtures cannot express) and compares them with the TypeScript
319
+ lowering of their twin in the same directory;
320
+ - loads and validates every history in `fixtures/histories/` and compares the
321
+ history contract with `@fluidgrids/workflows/history`;
322
+ - checks `fixtures/CHECKSUMS.sha256`, so a hand edit to a vendored file
323
+ (fixtures and the five schemas) fails.
324
+
325
+ `scripts/ts-reference/` pins the `@fluidgrids/workflows` release the
326
+ references come from. To pick up new fixtures, bump that pin (`package.json`
327
+ and `npm install --package-lock-only`), then:
328
+
329
+ ```bash
330
+ make ts-reference
331
+ make sync-fixtures NODE_SDK=../fluidgrids-workflows-sdk-node
332
+ ```
333
+
334
+ CI recomputes the TypeScript reference outputs from the pinned package
335
+ (`make verify-references`) and fails when they differ from the committed ones.
336
+
337
+ ## Development
338
+
339
+ ```bash
340
+ uv sync --locked
341
+ make lint # ruff check + ruff format --check
342
+ make typecheck # mypy --strict
343
+ make test # pytest with the 85% coverage gate
344
+ make sanity # all of the above, plus a build of the sdist and wheel
345
+ ```
346
+
347
+ Releases are CalVer (`YYYY.MDD.PATCH`) through `.github/workflows/release.yml`
348
+ (the shared G60 workflow): push a CalVer tag or run the workflow from the
349
+ Actions tab. The upload uses PyPI trusted publishing when the repository
350
+ variable `PYPI_TRUSTED_PUBLISHING` is `true`, otherwise the
351
+ `PYPI_BURDENOFF_TOKEN` secret; with neither, the publish job fails and names
352
+ the missing secret.
353
+
354
+ ## License
355
+
356
+ Proprietary. Copyright (c) Algoshred Technologies Private Limited. See `LICENSE`.