diff --git a/CLAUDE.md b/CLAUDE.md index f888556..5cf4a54 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,18 +1,20 @@ # Flowprint -Visual service blueprint editor. Produces `.flowprint.yaml` files consumed by code-search. +Visual service blueprint editor and workflow execution engine. Produces `.flowprint.yaml` specifications that can be edited visually, validated in CI, executed via the dev runner, and used to generate Temporal workflow code. ## Monorepo Structure ``` packages/ - schema/ @ruminaider/flowprint-schema (tsup -> ESM + CJS + .d.ts) - editor/ @ruminaider/flowprint-editor (Vite lib mode -> ESM + .d.ts + CSS) - cli/ flowprint (tsup -> CJS Node.js binary) - app/ flowprint-app (private) (Vite -> static site) + schema/ @ruminaider/flowprint-schema (tsup -> ESM + CJS + .d.ts) + engine/ @ruminaider/flowprint-engine (tsup -> ESM + CJS + .d.ts) + editor/ @ruminaider/flowprint-editor (Vite lib mode -> ESM + .d.ts + CSS) + cli/ flowprint (tsup -> CJS Node.js binary) + app/ flowprint-app (private) (Vite -> static site) + homepage/ @ruminaider/flowprint-homepage (private) (Vite -> static site) ``` -Dependency graph: `schema` has zero dependents. `editor` and `cli` depend on `schema`. `app` depends on `editor` + `schema`. Editor and CLI are siblings -- neither depends on the other. +Dependency graph: `engine` depends on `schema`. `cli` depends on `engine` + `schema`. `editor` depends on `schema`. `app` depends on `editor` + `schema`. `homepage` is standalone. Editor and engine are siblings -- neither depends on the other. ## Commands @@ -66,11 +68,11 @@ Both CLI and editor use tree-sitter WASM grammars. Pin exact versions to prevent - Node IDs in blueprints: `snake_case` (e.g., `complete_consultation`, `evaluate_treatment`) - npm scope: `@ruminaider` - File extension: `*.flowprint.yaml` -- Package names: `@ruminaider/flowprint-schema`, `@ruminaider/flowprint-editor`, `flowprint` (CLI) +- Package names: `@ruminaider/flowprint-schema`, `@ruminaider/flowprint-engine`, `@ruminaider/flowprint-editor`, `flowprint` (CLI) ## Node Types -Six types: `action`, `switch`, `parallel`, `wait`, `error`, `terminal`. Edges are implicit in node definitions (`next`, `cases[].next`, `branches[]`, `join`, `error.catch`), not stored separately. +Seven types: `action`, `switch`, `parallel`, `wait`, `error`, `terminal`, `trigger`. Edges are implicit in node definitions (`next`, `cases[].next`, `branches[]`, `join`, `error.catch`, `default`, `timeout_next`), not stored separately. Trigger nodes declare how a workflow starts (`trigger_type`: `schedule`, `webhook`, `event`, `manual`) and point to the first node via `next`. ## Code Style diff --git a/docs/adr/002-six-typed-node-types.md b/docs/adr/002-six-typed-node-types.md index c81d7fe..d30d43e 100644 --- a/docs/adr/002-six-typed-node-types.md +++ b/docs/adr/002-six-typed-node-types.md @@ -4,6 +4,8 @@ Accepted (amended 2026-03-02 to add trigger node) +Note: The filename retains the original `002-six-typed-node-types` for URL stability. The ADR was amended to add the seventh node type (trigger). + ## Date 2025-01-15 diff --git a/docs/adr/008-design-time-specification-architecture.md b/docs/adr/008-design-time-specification-architecture.md new file mode 100644 index 0000000..59c20dc --- /dev/null +++ b/docs/adr/008-design-time-specification-architecture.md @@ -0,0 +1,95 @@ +# ADR-008: Design-Time Specification Architecture + +## Status + +Accepted (amended by ADR-011) + +## Date + +2026-03-03 + +## Context + +Business workflows — the rules governing how orders are processed, patients are routed, claims are adjudicated — are among the highest-value logic in any application. Yet this logic is routinely buried inside application source code, invisible to the people who define it and difficult to reason about for the people who maintain it. + +This creates three compounding problems: + +1. **Business logic is invisible.** A 200-line TypeScript function implementing an order routing workflow encodes critical business decisions, but a product manager, operations lead, or even a new developer cannot look at the source and understand the flow. The "what" and "why" of the business process are lost in implementation details — error handling, SDK calls, type conversions, retry logic. Reviewing changes to this logic in a pull request requires deep familiarity with the codebase. + +2. **Design artifacts and running code are disconnected.** Teams frequently design workflows in tools like Mermaid, Lucidchart, or Notion — but these diagrams are documentation, not specifications. They cannot be validated, tested, or executed. The diagram and the code diverge within days of implementation. There is no tool that produces a visual design artifact that is also the source of truth for code generation. + +3. **Runtime platforms own your logic.** Tools that do bridge visual design and execution — n8n, Camunda, AWS Step Functions, Windmill — require running your workflow on their platform. The visual representation is coupled to their runtime. If you outgrow the platform, migrate to a different execution model, or need to embed the logic in your own application, you face a rewrite. Business logic becomes a platform dependency rather than an engineering asset. + +### Competitive landscape + +We evaluated the existing ecosystem of workflow tools and found that no existing tool simultaneously provides: + +- A **visual editor** for designing workflows +- A **human-readable, declarative artifact** (not BPMN XML, not proprietary JSON) designed for version control +- **Code generation** targeting developer-owned application code (not platform-locked execution) +- **Integrated decision tables** for business rules +- A **dev runner** for local testing without infrastructure +- **Zero vendor lock-in** — the artifact is portable, the generated code runs on standard infrastructure + +| Tool | Visual editor | Readable artifact | Code generation | Decision tables | Vendor-free | +|------|--------------|-------------------|----------------|----------------|-------------| +| n8n | Yes | No (JSON export) | No | No | Yes | +| Temporal | No | N/A (code-first) | N/A | No | Yes | +| Windmill | Yes | Yes (YAML) | No | No | Yes | +| AWS Step Functions | Yes | Partial (ASL) | No | No | No | +| Camunda | Yes | No (BPMN XML) | No | Yes (DMN) | Partial | +| GoRules | Yes (rules only) | Yes (JSON) | No | Yes | Yes | + +Windmill comes closest with YAML-based flow definitions and a visual editor, but its YAML is coupled to the Windmill runtime — it cannot generate portable application code. Camunda shares the philosophy of separating design from implementation and includes DMN decision tables, but its artifact format (BPMN XML) is verbose and unreadable in version control diffs, and it requires deploying the Zeebe engine. AWS Step Functions Workflow Studio produces a declarative artifact (ASL), but ASL mixes business logic with AWS infrastructure references and is locked to the AWS ecosystem. + +The gap is a tool that treats workflow design as a **specification activity** — producing a portable, human-readable artifact that bridges business stakeholders and developers — and then generates code targeting the team's own infrastructure. + +## Decision + +We adopt a design-time specification architecture where the blueprint is a portable, declarative artifact that bridges business intent and executable code: + +1. **Flowprint is an integrated design-to-execution pipeline.** The YAML artifact flows through every stage — visual editing, validation, simulation, execution — and each stage adds value without modifying the artifact's canonical form. No single stage is the product; the seamless chain from design through execution is. *(Planned per ADR-011: the pipeline now includes direct runtime execution via an embedded engine, not just code generation.)* + +2. **The specification is runtime-agnostic.** Blueprints and rules files describe business logic without encoding runtime-specific semantics. The embedded execution engine loads blueprints at runtime and orchestrates them directly. Durability adapters (Temporal, plain in-process) are pluggable — the blueprint does not change based on the execution target. Code generation to Temporal is retained as an optional eject path. Runtime-specific metadata (timeouts, retry policies, task queues) lives in namespaced sections that adapters consume and other tools ignore. *(Planned per ADR-011: the primary developer experience shifts from code generation to engine execution.)* + +3. **Business stakeholders own flow logic; developers own execution.** The visual editor and decision table editor are designed so that business analysts, operations leads, and service managers can define processes, routing rules, and SOPs without developer involvement. Developers register handler functions for side-effect nodes (API calls, database writes) and choose a durability adapter. Neither audience depends on the other for their core work, but both collaborate on the same artifact — the engine is the bridge. + +4. **Keep the schema lean; push complexity into tooling.** The YAML specification stays flat, declarative, and readable in a pull request diff. Sophistication belongs in the editor (visual abstractions, simulation), the engine (expression evaluation, rule interpretation), and the code generators (idiomatic output) — not in the file format. A business stakeholder should be able to read a `.flowprint.yaml` and understand the flow without technical context. + +5. **Git is the system of record.** Blueprints and rules are plain files designed for version control — deterministic serialization produces clean diffs, separate `.rules.yaml` files enable independent changesets, and the canonical serializer enforces consistent formatting. There is no database, no server state, and no proprietary storage format. If it's not in the repository, it doesn't exist. + +## Consequences + +### Positive + +- Business workflows are visible and reviewable. A product manager can look at a `.flowprint.yaml` file (or its visual rendering) and understand the flow without reading application code. Pull requests that change business logic show readable YAML diffs, not opaque code changes. + +- The blueprint is a shared language between roles. Developers, architects, and business stakeholders collaborate on the same artifact. The visual editor makes it accessible; the YAML makes it precise; the schema makes it validatable. + +- Teams own their code and their infrastructure. Generated code is standard TypeScript that lives in the team's repository and runs on their infrastructure. There is no runtime dependency on Flowprint itself — if the team stops using Flowprint tomorrow, their generated code continues to work unchanged. + +- Code generation targets are extensible. Because the blueprint is a declarative specification (not an execution format), new code generation targets can be added without changing the blueprint format. A team could generate Temporal workflows today and Inngest functions tomorrow from the same `.flowprint.yaml`. + +- Decision tables bring business rules into the specification. Rules that would otherwise be buried in conditional logic are expressed declaratively in `.rules.yaml` files, linked to workflow nodes, and evaluated during both simulation and code generation. Business stakeholders can read and validate these rules directly. + +- The dev runner and browser simulator provide fast feedback. Teams can test workflow logic locally in milliseconds without deploying infrastructure, reducing the iteration cycle from "deploy and observe" to "edit and simulate." + +### Negative + +- ~~Flowprint is not a runtime. Teams must still choose, deploy, and operate an execution engine (Temporal, or a future supported target). Flowprint reduces the cost of writing the workflow code but does not eliminate the operational complexity of running it.~~ *(Planned per ADR-011: Flowprint is now an embeddable execution engine. Teams embed the engine directly; Temporal is an optional durability adapter.)* + +- ~~The "design then generate" model introduces a synchronization gap. After initial generation, changes to the blueprint require regenerating code and manually merging with any hand-written modifications. There is no incremental update mechanism.~~ *(Planned per ADR-011: the engine loads blueprints at runtime, eliminating the generate-then-own synchronization gap.)* + +- ~~The target audience sits at an intersection that may be narrow. Teams comfortable with Temporal tend to be code-first and may not see value in a visual design layer. Teams wanting visual workflow design tend to expect an integrated runtime. Flowprint must convince both groups that the specification-first model is worth the extra step.~~ *(Planned per ADR-011: Flowprint now provides both the visual design layer AND an integrated runtime, addressing both audience expectations.)* + +- ~~Supporting multiple code generation targets adds maintenance burden. Each target requires its own generator, its own test suite, and its own documentation. The blueprint format must remain general enough to map to different execution models without becoming lowest-common-denominator.~~ *(Planned per ADR-011: the engine executes blueprints directly. Durability adapters are thin wrappers around the same engine, not full parallel code generators.)* + +- The visual editor is a significant engineering investment. Building and maintaining a production-quality React-based workflow editor is a substantial ongoing commitment, independent of the specification and code generation work. + +## Amendments + +- **ADR-011** (2026-03-08, status: Proposed): Flowprint transitions from a + specification compiler to an embeddable execution engine with GoRules ZEN. + The amendments below reflect the intended future state once ADR-011 is + implemented. See ADR-011 for the full rationale and current implementation + status. diff --git a/docs/adr/README.md b/docs/adr/README.md index 5c008f9..709a12f 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -18,3 +18,4 @@ We follow the [Nygard ADR template](https://cognitect.com/blog/2011/11/15/docume | [008](008-design-time-specification-architecture.md) | Design-Time Specification Architecture | Accepted | 2025-12-01 | | [009](009-decision-tables-and-rules-engine.md) | Decision Tables and Rules Engine | Accepted | 2026-03-02 | | [010](010-browser-safe-simulation-engine.md) | Browser-Safe Simulation Engine | Accepted | 2026-03-02 | +| [011](011-execution-engine-with-embedded-gorules.md) | Execution Engine with Embedded GoRules | Proposed | 2026-03-08 | diff --git a/docs/cli-reference.md b/docs/cli-reference.md index 3fd8154..c1e9821 100644 --- a/docs/cli-reference.md +++ b/docs/cli-reference.md @@ -1,6 +1,6 @@ # Flowprint CLI Reference -The `flowprint` CLI validates, lints, diffs, and scaffolds `.flowprint.yaml` service blueprints. +The `flowprint` CLI validates, lints, diffs, runs, generates, tests, and scaffolds `.flowprint.yaml` service blueprints. ## Installation @@ -108,22 +108,6 @@ Reports: | ---- | ------------------------------ | | 0 | Always (informational command) | -### `flowprint migrate` - -Migrate `.flowprint.yaml` files to the latest schema version. - -```sh -flowprint migrate -``` - -Currently a placeholder that reports the latest version. - -**Exit codes:** - -| Code | Meaning | -| ---- | ------------------------- | -| 0 | Already at latest version | - ### `flowprint init [name]` Create a starter `.flowprint.yaml` blueprint with interactive prompts. @@ -156,6 +140,85 @@ Uses `serialize()` from `@ruminaider/flowprint-schema` to produce canonical YAML | 0 | Blueprint created successfully | | 2 | File already exists | +### `flowprint run ` + +Execute a blueprint using the dev runner. Validates schema and expressions first, then walks the graph — evaluating switch conditions via sandboxed expressions, running parallel branches concurrently, and loading entry point functions via dynamic import. Produces an execution trace showing the path taken and output at each step. + +```sh +flowprint run my-service.flowprint.yaml +flowprint run my-service.flowprint.yaml --input '{"customer": "test"}' +flowprint run my-service.flowprint.yaml --fixtures fixtures.json --json +``` + +**Options:** + +| Option | Description | +| --------------------------- | -------------------------------------------------- | +| `--input ` | Workflow input as JSON string (default: `"{}"`) | +| `--fixtures ` | Path to JSON fixtures file for wait nodes | +| `--json` | Output structured JSON trace | +| `--expression-timeout ` | Expression evaluation timeout in ms (default: `1000`) | + +**Engine functions used:** `runGraph()`, `validateExpressions()`, `loadFixtures()`, `formatTrace()` + +**Exit codes:** + +| Code | Meaning | +| ---- | ----------------------------- | +| 0 | Success | +| 1 | Execution failure | +| 2 | File not found or parse error | + +### `flowprint generate ` + +Generate Temporal TypeScript workflow code from a blueprint. Creates workflow definitions, activity stubs, worker configuration, type definitions, and test fixture files in the output directory. + +```sh +flowprint generate my-service.flowprint.yaml +flowprint generate my-service.flowprint.yaml --output ./src/workflows +``` + +**Options:** + +| Option | Description | +| ---------------- | --------------------------------------- | +| `--output ` | Output directory (default: `./generated`) | + +**Engine functions used:** `generateCode()` + +**Exit codes:** + +| Code | Meaning | +| ---- | ----------------------------- | +| 0 | Success | +| 1 | Validation errors | +| 2 | File not found or parse error | + +### `flowprint test [glob]` + +Run decision table test files against their corresponding rules. Auto-derives `.rules.yaml` from `.rules.test.yaml` filenames (e.g., `pricing.rules.test.yaml` tests `pricing.rules.yaml`). + +```sh +flowprint test +flowprint test "src/**/*.rules.test.yaml" +``` + +**Arguments:** + +| Argument | Description | +| -------- | ------------------------------------------------------- | +| `[glob]` | Glob pattern for test files (default: `**/*.rules.test.yaml`) | + +**Engine functions used:** `runRulesTests()` + +**Exit codes:** + +| Code | Meaning | +| ---- | ----------------------------- | +| 0 | All tests passed | +| 1 | Test failures | +| 2 | File not found or parse error | + ## Global Exit Codes | Code | Meaning | diff --git a/docs/getting-started.md b/docs/getting-started.md index 2675c7a..ef8a747 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -95,7 +95,7 @@ Lanes are horizontal bands on the canvas. Double-click a lane header to rename i ### Nodes -Add nodes from the **floating toolbar** at the bottom of the canvas, or use the Command Palette (Cmd+K / Ctrl+K) to search for node types. Six node types are available: +Add nodes from the **floating toolbar** at the bottom of the canvas, or use the Command Palette (Cmd+K / Ctrl+K) to search for node types. Seven node types are available: | Type | Purpose | | ---------- | ---------------------------------------------------------- | @@ -105,6 +105,7 @@ Add nodes from the **floating toolbar** at the bottom of the canvas, or use the | `wait` | Pauses until an event occurs or a timeout expires | | `error` | Handles errors from upstream action nodes | | `terminal` | End state of the flow (`success` or `failure`) | +| `trigger` | Declares how a workflow starts (schedule, webhook, event, manual) | ### Connections @@ -173,7 +174,67 @@ To also verify that entry point file paths and symbols exist on disk: flowprint validate my-service.flowprint.yaml --check-entry-points ``` -## 7. Lint Your Blueprint (Optional) +## 7. Run Your Blueprint + +The dev runner executes entry point functions and walks the graph, producing a trace of every node visited and the output at each step. + +```bash +flowprint run my-service.flowprint.yaml --input '{"customer": "test"}' +``` + +Sample trace output: + +``` + ✓ start_action → { processed: true } + ✓ end_success (terminal: success) + + 2 nodes executed in 12ms +``` + +Use `--json` for structured output, or `--fixtures` to provide mock data for wait nodes. + +## 8. Generate Temporal Code (Optional) + +Generate Temporal TypeScript workflow scaffolding from your blueprint: + +```bash +flowprint generate my-service.flowprint.yaml --output ./generated +``` + +This creates: + +- `workflow.ts` — workflow definition +- `activities.ts` — activity stubs for each action node +- `worker.ts` — worker configuration +- `types.ts` — TypeScript type definitions +- `test-fixtures.ts` — test fixture helpers + +The generated code is a starting point, not a runtime dependency. Teams own and extend the output. + +## 9. Test Decision Tables (Optional) + +If your blueprint uses decision tables (`.rules.yaml` files), create a corresponding test file: + +```yaml +# pricing.rules.test.yaml +tests: + - name: standard customer gets base price + input: + customer_type: standard + order_total: 100 + expected: + discount: 0 +``` + +Run all test files: + +```bash +flowprint test +``` + +The test runner auto-derives `pricing.rules.yaml` from `pricing.rules.test.yaml`. + +## 10. Lint Your Blueprint (Optional) ```bash flowprint lint my-service.flowprint.yaml @@ -198,7 +259,7 @@ rules: no-empty-branches: error ``` -## 8. Set Up CI Validation (Optional) +## 11. Set Up CI Validation (Optional) Add a GitHub Actions workflow to validate blueprints on every push and pull request: @@ -220,7 +281,7 @@ jobs: - run: flowprint validate '**/*.flowprint.yaml' ``` -## 9. Embed in Your Own App (Optional) +## 12. Embed in Your Own App (Optional) The editor is available as a standalone React component for embedding in your own applications: @@ -251,5 +312,6 @@ For the full API reference, theming details, symbol search configuration, and re - [YAML Format Reference](yaml-format-reference.md) -- full schema documentation for `.flowprint.yaml` files - [CLI Reference](cli-reference.md) -- all CLI commands, options, and exit codes +- [Rules Format Reference](rules-format-reference.md) -- decision table schema and test file format - [Deployment Guide](deployment.md) -- host the standalone app with Docker, Vercel, Cloudflare Pages, AWS, GCP, or GitHub Pages - [Embedding Guide](../packages/editor/docs/EMBEDDING_GUIDE.md) -- embed the editor component in your React application diff --git a/docs/rules-format-reference.md b/docs/rules-format-reference.md new file mode 100644 index 0000000..581b0d5 --- /dev/null +++ b/docs/rules-format-reference.md @@ -0,0 +1,308 @@ +# Flowprint Rules Format Reference + +Version: `flowprint-rules/1.0` + +File extension: `*.rules.yaml` + +Decision tables define condition-action rules for flowprint nodes. They are evaluated by the engine at runtime (`flowprint run`) and by the browser-safe evaluator in the editor. Rules files are referenced from blueprint nodes via the `rules` field. + +## Top-Level Structure + +| Field | Type | Required | Description | +| ------------- | ---------- | -------- | ------------------------------------------------------------------------ | +| `schema` | string | yes | Must be `flowprint-rules/1.0`. | +| `name` | string | yes | Machine-readable name for this rules file. | +| `description` | string | no | Human-readable description of the decision table. | +| `hit_policy` | enum | yes | How to handle multiple matching rules (see Hit Policies below). | +| `inputs` | InputDef[] | no | Input definitions: simple dot-paths or labeled expressions. | +| `rules` | Rule[] | yes | Decision rules evaluated in order. At least one. | + +## Inputs + +Inputs declare the values evaluated by rule conditions. Two forms are supported: + +### Simple dot-path + +A string that resolves a nested property from the execution context via dot notation. + +```yaml +inputs: + - order.total_amount + - customer.status +``` + +`order.total_amount` resolves to `context.order.total_amount`. Parts are split on `.` and traversed left-to-right. Returns `undefined` if the path does not exist. + +### Labeled expression + +An object with `label` and `expr` fields. The expression is evaluated via the Node.js `vm` module with a configurable timeout. + +```yaml +inputs: + - label: Is VIP + expr: customer.loyalty_points > 1000 +``` + +| Field | Type | Required | Description | +| ------- | ------ | -------- | ------------------------------------ | +| `label` | string | yes | Human-readable label for this input. | +| `expr` | string | yes | Expression to compute the value. | + +**Note:** Labeled expressions are only supported in the Node.js evaluator (CLI, dev runner). The browser-safe evaluator in the editor skips them. + +## Rules + +Each rule has an optional `when` clause (conditions) and a required `then` clause (output). + +| Field | Type | Required | Description | +| ---------- | ------- | -------- | ---------------------------------------------------------------------------- | +| `when` | object | no | Conditions keyed by input name. Omit for a wildcard rule that always matches. | +| `then` | object | yes | Output values produced when this rule matches. Any shape. | +| `priority` | integer | no | For `priority` hit policy only. Lower number = higher priority. Minimum: 0. | + +A rule matches when **all** conditions in `when` evaluate to true. Missing fields are treated as wildcards (match any value). A rule with no `when` clause always matches. + +## Conditions + +Condition values in `when` can be either a shorthand scalar or an operator object. + +### Shorthand scalar + +A plain value is equivalent to `{ eq: value }`: + +```yaml +when: + status: premium # same as { eq: "premium" } + is_active: true # same as { eq: true } + count: 5 # same as { eq: 5 } + nullable_field: null # same as { eq: null } +``` + +### Operator object + +Multiple operators on the same field are ANDed together: + +```yaml +when: + order_total: + gte: 100 + lte: 500 + status: + in: [premium, enterprise] +``` + +## Operators + +All operators use strict comparison (no type coercion). + +| Operator | Operand Type | Description | +| --------- | ----------------- | ----------------------------------------- | +| `eq` | any | Value equals operand (`===`). | +| `not_eq` | any | Value does not equal operand (`!==`). | +| `gt` | number | Value is greater than operand. | +| `gte` | number | Value is greater than or equal. | +| `lt` | number | Value is less than operand. | +| `lte` | number | Value is less than or equal. | +| `in` | array | Value is one of the listed items. | +| `not_in` | array | Value is not one of the listed items. | +| `between` | [number, number] | Value is between min and max (inclusive). | + +Numeric operators (`gt`, `gte`, `lt`, `lte`, `between`) require both value and operand to be `number`. If either is not a number, the condition evaluates to `false`. + +## Hit Policies + +The `hit_policy` field controls how the evaluator handles matching rules. + +| Hit Policy | Output Type | Behavior | +| ---------- | ----------- | -------------------------------------------------------------- | +| `first` | object | Returns the `then` of the first matching rule. `{}` if none. | +| `collect` | object[] | Returns the `then` of all matching rules as an array. | +| `all` | object[] | Like `collect`, but throws an error if no rules match. | +| `priority` | object | Returns the `then` of the highest-priority match. `{}` if none. | + +For `priority`, rules are sorted by their `priority` field (lower number = higher priority, default `0`), and the first in sorted order is returned. + +## Blueprint Integration + +Blueprint nodes reference rules files via the `rules` field, available on `action` and `switch` nodes: + +```yaml +nodes: + route_order: + type: switch + lane: backend + label: Route Order + rules: + file: rules/order-routing.rules.yaml + evaluator: builtin +``` + +| Field | Type | Required | Description | +| ----------- | ------ | -------- | ------------------------------------------------------------- | +| `file` | string | yes | Path to `.rules.yaml` file, resolved relative to the blueprint file's directory. | +| `evaluator` | string | no | Evaluator plugin name. Default: `builtin`. | + +## Test Files + +Test files validate rules against known inputs and expected outputs. They use the `flowprint-rules-test/1.0` schema. + +File extension: `*.rules.test.yaml` + +**Naming convention:** `.rules.test.yaml` tests `.rules.yaml` in the same directory. The `flowprint test` command auto-derives the rules file by replacing `.rules.test.yaml` with `.rules.yaml`. + +### Top-Level Structure + +| Field | Type | Required | Description | +| -------- | ------------ | -------- | -------------------------------------------- | +| `schema` | string | yes | Must match `flowprint-rules-test/X.Y`. | +| `tests` | TestCase[] | yes | Array of test cases. At least one. | + +### Test Case + +| Field | Type | Required | Description | +| ---------------------- | ------- | -------- | ------------------------------------------ | +| `name` | string | yes | Human-readable test case name. | +| `input` | object | yes | Input object provided to the evaluator. | +| `expected_output` | object | no | Expected output to compare against actual. | +| `expected_matched_count` | integer | no | Expected number of matched rules. | + +A test passes when all specified expectations are met. Comparison uses JSON stringification. + +### Running Tests + +```sh +flowprint test # runs **/*.rules.test.yaml +flowprint test "src/**/*.rules.test.yaml" # custom glob +``` + +Exit codes: `0` all passed, `1` failures, `2` file/parse error. + +## Examples + +### Discount rules (`first` hit policy) + +```yaml +# order-discount.rules.yaml +schema: flowprint-rules/1.0 +name: order-discount +description: Discount rules for order processing +hit_policy: first +inputs: + - order_total +rules: + - when: + order_total: + gte: 200 + then: + discount: 25 + + - when: + order_total: + gte: 100 + then: + discount: 10 + + - then: + discount: 0 +``` + +```yaml +# order-discount.rules.test.yaml +schema: flowprint-rules-test/1.0 +tests: + - name: large order gets 25% discount + input: + order_total: 250 + expected_output: + discount: 25 + + - name: medium order gets 10% discount + input: + order_total: 150 + expected_output: + discount: 10 + + - name: small order gets no discount + input: + order_total: 50 + expected_output: + discount: 0 +``` + +### Order routing (`first` hit policy with `in` operator) + +```yaml +schema: flowprint-rules/1.0 +name: order_routing +hit_policy: first +inputs: + - order_type +rules: + - when: + order_type: + in: [test, expanded_test] + then: + next: check_bulk_order + + - when: + order_type: prescription_treatment + then: + next: resolve_prescriptions + + - when: {} + then: + next: handle_unknown +``` + +### Fee collection (`collect` hit policy) + +```yaml +schema: flowprint-rules/1.0 +name: applicable_fees +hit_policy: collect +inputs: + - order.amount + - customer.region +rules: + - when: + order.amount: + gte: 500 + then: + fee_type: premium_processing + amount: 50 + + - when: + customer.region: international + then: + fee_type: international_surcharge + amount: 25 +``` + +With input `{ order: { amount: 600 }, customer: { region: "international" } }`, both rules match and the output is an array of both `then` objects. + +### Escalation (`priority` hit policy) + +```yaml +schema: flowprint-rules/1.0 +name: escalation +hit_policy: priority +rules: + - when: + severity: critical + then: + route: executive_team + priority: 0 + + - when: + severity: high + then: + route: senior_team + priority: 1 + + - when: {} + then: + route: general_queue + priority: 10 +``` + +With input `{ severity: "critical" }`, both the `critical` rule and the wildcard match, but `priority: 0` wins. diff --git a/docs/why-flowprint.md b/docs/why-flowprint.md index 999f6a6..892862b 100644 --- a/docs/why-flowprint.md +++ b/docs/why-flowprint.md @@ -59,14 +59,14 @@ Output: rendered diagrams from text DSLs. Flowprint is an open-source bridge between service design and service implementation. It introduces a structured, machine-readable format for service -blueprints -- `.flowprint.yaml` -- and provides tooling to edit, validate, and -generate code from that format. +blueprints -- `.flowprint.yaml` -- and provides tooling to edit, validate, +execute, test, and generate code from that format. ### Schema-first A `.flowprint.yaml` file is not a picture. It is a structured document with a published JSON Schema. It can be validated, linted, diffed, and processed by any -tool that reads YAML. The schema defines six node types that map directly to +tool that reads YAML. The schema defines seven node types that map directly to service blueprint semantics: customer actions, frontstage interactions, backstage processes, support processes, physical evidence, and external systems. @@ -84,6 +84,21 @@ workflow scaffolding: workflow definitions, activity stubs, worker configuration and type definitions. The generated code is a starting point, not a runtime dependency. Teams own and extend the output. +### Execution + +`flowprint run` executes blueprints locally using the dev runner. The graph +walker processes nodes in dependency order, evaluates switch conditions via +sandboxed expressions, runs parallel branches concurrently, and produces an +execution trace showing the path taken and output at each step. Entry point +functions are loaded dynamically from the codebase. + +### Decision tables + +Business rules are declared in `.rules.yaml` files alongside blueprints. +Decision tables use structured conditions (operators, hit policies) that are +readable by non-developers and testable independently via `flowprint test`. The +browser-safe evaluator provides instant feedback in the editor. + ### Embeddable `` is an npm package. It can be embedded in internal tools, @@ -92,8 +107,8 @@ a focused API, not a standalone application that requires its own infrastructure ### Focused vocabulary -Six typed node types cover service blueprint semantics without the complexity of -full BPMN notation. Edges are implicit (derived from node relationships), and +Seven typed node types cover service blueprint semantics without the complexity +of full BPMN notation. Edges are implicit (derived from node relationships), and the canonical serializer ensures that the YAML output stays clean and diff-friendly. The goal is a format that service designers can read and engineers can generate from -- not a general-purpose diagramming language. diff --git a/docs/yaml-format-reference.md b/docs/yaml-format-reference.md index efaa300..403a451 100644 --- a/docs/yaml-format-reference.md +++ b/docs/yaml-format-reference.md @@ -34,7 +34,7 @@ Every node is keyed by its ID (snake_case convention, e.g. `create_prescription` | Field | Type | Required | Description | | -------------- | ------------------- | -------- | ----------------------------------------------------------- | -| `type` | enum | yes | One of the 6 node types listed below. | +| `type` | enum | yes | One of the 7 node types listed below. | | `lane` | string | yes | Lane ID this node belongs to. | | `label` | string | yes | Human-readable display name. | | `description` | string | no | Longer description of what this node does. Not on terminal. | @@ -112,6 +112,56 @@ End state of the flow. Has no outgoing edges. **Note:** Terminal nodes do not support `description` or `entry_points`. +### Node Type: `trigger` + +Declares how a workflow starts. Trigger nodes have no incoming edges and don't execute logic — they define the workflow's activation mechanism. Only one trigger-type-specific configuration block (`schedule`, `webhook`, `event`, or `manual`) is allowed per trigger node, matching the `trigger_type` value. + +| Field | Type | Required | Description | +| -------------- | ------ | -------- | -------------------------------------------------------- | +| `trigger_type` | enum | yes | `schedule`, `webhook`, `event`, or `manual`. | +| `next` | string | yes | Node ID that this trigger initiates. | +| `schedule` | object | cond. | Required when `trigger_type` is `schedule`. | +| `webhook` | object | cond. | Required when `trigger_type` is `webhook`. | +| `event` | object | cond. | Required when `trigger_type` is `event`. | +| `manual` | object | cond. | Required when `trigger_type` is `manual`. | + +**Schedule configuration:** + +| Field | Type | Required | Description | +| ---------- | ------ | -------- | -------------------------------- | +| `cron` | string | no | Cron expression. | +| `timezone` | string | no | IANA timezone (e.g. `UTC`). | + +**Webhook configuration:** + +| Field | Type | Required | Description | +| --------- | ------------------- | -------- | ------------------------------------------------ | +| `method` | enum | no | `GET`, `POST`, `PUT`, `PATCH`, or `DELETE`. | +| `path` | string | no | URL path. | +| `headers` | map\ | no | Required headers. | + +**Event configuration:** + +| Field | Type | Required | Description | +| -------- | ------ | -------- | ------------------------ | +| `source` | string | no | Event source identifier. | +| `type` | string | no | Event type. | +| `filter` | string | no | Filter expression. | + +**Manual configuration:** + +| Field | Type | Required | Description | +| ------------- | ------------ | -------- | --------------- | +| `form_fields` | FormField[] | no | Input fields. | + +Each **FormField**: + +| Field | Type | Required | Description | +| ---------- | ------- | -------- | ----------------------------------------- | +| `name` | string | no | Field name. | +| `type` | enum | no | `string`, `number`, or `boolean`. | +| `required` | boolean | no | Whether the field is required. | + ## ErrorHandler Configured on `action` nodes via the `error` field. @@ -132,15 +182,15 @@ Configured on `action` nodes via the `error` field. Edges are implicit in node definitions -- they are not stored as a separate section. The graph is derived from these fields: -| Source Field | Node Types | Edge Type | -| -------------- | ------------------- | --------- | -| `next` | action, wait, error | `normal` | -| `cases[].next` | switch | `normal` | -| `default` | switch | `default` | -| `branches[]` | parallel | `normal` | -| `join` | parallel | `normal` | -| `timeout_next` | wait | `normal` | -| `error.catch` | action | `error` | +| Source Field | Node Types | Edge Type | +| -------------- | ---------------------------- | ---------- | +| `next` | action, wait, error, trigger | `normal` | +| `cases[].next` | switch | `normal` | +| `default` | switch | `default` | +| `branches[]` | parallel | `normal` | +| `join` | parallel | `normal` | +| `timeout_next` | wait | `normal` | +| `error.catch` | action | `error` | ## Structural Validation Rules @@ -149,6 +199,7 @@ Beyond schema validation, the following structural rules are enforced: 1. **Dangling references**: All node ID references (`next`, `cases[].next`, `branches[]`, `join`, `error.catch`, `default`, `timeout_next`) must point to existing nodes. 2. **Lane references**: Every node's `lane` must match a key in the `lanes` map. 3. **Orphan detection**: Non-terminal nodes with no incoming and no outgoing edges are flagged. Terminal nodes with no incoming edges (in multi-node documents) are flagged. +4. **Trigger constraints**: Trigger nodes must have no incoming edges — they are always entry points of the workflow. ## Canonical Serialization @@ -160,14 +211,15 @@ The `serialize()` function in `@ruminaider/flowprint-schema` is the only sanctio **Node keys** (in order): `type`, `lane`, `label`, `description`, `metadata`, `entry_points`, then type-specific fields in this order: -| Node Type | Type-specific key order | -| ---------- | ------------------------------------------ | -| `action` | `next`, `error` | -| `switch` | `cases`, `default` | -| `parallel` | `branches`, `join`, `join_strategy` | -| `wait` | `event`, `timeout`, `next`, `timeout_next` | -| `error` | `next` | -| `terminal` | `outcome` | +| Node Type | Type-specific key order | +| ---------- | ------------------------------------------------------------- | +| `action` | `next`, `error` | +| `switch` | `cases`, `default` | +| `parallel` | `branches`, `join`, `join_strategy` | +| `wait` | `event`, `timeout`, `next`, `timeout_next` | +| `error` | `next` | +| `terminal` | `outcome` | +| `trigger` | `trigger_type`, `next`, `schedule`, `webhook`, `event`, `manual` | ### Formatting Rules