Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 99 additions & 1 deletion .changeset/pre.json

Large diffs are not rendered by default.

160 changes: 160 additions & 0 deletions examples/app-crm/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,165 @@
# @objectstack/example-crm

## 4.0.92-rc.2

### Patch Changes

- 5293114: fix(automation): a decision's three declared ways to route a branch are now one working model (#4414)

A `decision` node advertised three mechanisms for splitting a path and only one
of them did anything. The other two were the ADR-0049 `declared ≠ enforced`
shape, and the pair of them shipped a guard that does not guard in
`examples/app-crm`.

| mechanism | before | now |
| :--------------------------------------------------- | :----------------------------------------------------------------------------------------------------- | :---------------------------------------------------- |
| `edge.condition` | ✅ the only one that worked | unchanged |
| `edge.isDefault` | **zero readers** anywhere but the schema declaration | BPMN default flow, enforced in `traverseNext` |
| `decision.config.conditions[].label` → `branchLabel` | matched **0** out-edge labels across every example app, then fell back to the full edge set in silence | routes; an unclaimable label is logged, not swallowed |

## What was broken, end to end

`crm_convert_lead_wizard` means "already converted → abort screen; otherwise →
the wizard". It ran **both**: an already-converted lead got
"This lead has already been converted" and then walked straight into the
conversion wizard behind it. Four independent silences stacked up:

1. the decision's first condition was authored `{lead_record.status} ==
'converted'` — braces in a slot declared bare CEL, so it was string-compared
and never true;
2. the second (`'true'`) therefore won, yielding `branchLabel: 'No — proceed'`;
3. no out-edge carried that label (they were `'Yes'` / `'No'`), so traversal
discarded the branch and considered every out-edge;
4. `e3b` was unconditional, so it ran regardless — and the natural fix, marking
it `isDefault: true`, was a dead key.

## The model

`branchLabel` narrows the edge set → `condition` gates each edge → `isDefault`
catches whatever is left. Concretely:

- **`isDefault` is enforced.** A default edge is traversed only when no
conditional sibling of the same source node matched, and it is no longer part
of the unconditional parallel fan-out — that distinction is the whole point of
the marker. Passed over because a real branch won, its target records the same
`skipped` step a closed gate does (#4354).
- **An unclaimable branch label warns.** Traversal still falls back to the full
edge set (a run mid-flight must not die on a metadata error) but says so,
naming the computed branch and the out-edge labels that exist.
- **A decision that declares no `conditions` reports no branch.** It used to
report `'default'` unconditionally — a label no out-edge in the repo ever
carried — which is why every decision node fell back to the full edge set.
The `'default'` sentinel survives for the case it actually describes (declared
conditions, none matched) and is now claimed by the `isDefault` edge as well
as by an edge literally labelled `'default'`.
- **`conditions[].expression` is evaluated as the bare CEL it is declared to
be.** The raw string went to the legacy `{var}` template path, where
`lead.status == 'converted'` cannot resolve and the branch is decided by
string comparison. Unlike `edge.condition` this slot carries no
`ExpressionInput` envelope — the decision descriptor is deliberately
schemaless — so the executor supplies the dialect. A brace-in-CEL predicate
now fails loudly (ADR-0032 §1c) instead of deciding `false`.

## Caught at authoring time too

Four new `os build` / `os validate` warnings, because a wrong route is silent at
run time by nature (Prime Directive #12):

`flow-branch-label-unmatched` (the shipped shape),
`flow-decision-unconditional-branch` (a guarded decision with an unconditional
sibling — the actual hole), `flow-default-edge-with-condition` and
`flow-multiple-default-edges`.

Both of the first two fire on the pre-fix `convert-lead.flow.ts` and are silent
after it.

## Effect on flows that already exist

Enforcing `isDefault` changes how a **stored** flow behaves, and the flows it
changes are mostly Studio's own. `objectui`'s flow edge inspector has always
written `isDefault: true` when you bind an out-edge to a decision's default/else
branch — into a key with zero readers, so that edge ran unconditionally, in
parallel with whichever branch actually matched. Those flows now take exactly
one branch. That is the fix, but it is a behaviour change on existing data
rather than only on newly authored metadata, so it is worth knowing before
upgrading: a flow that quietly ran two paths will now run one.

Nothing changes for an edge that never carried the marker — `isDefault` defaults
to `false`, and an ordinary unconditional out-edge still fans out in parallel
exactly as before.

## The example app

`crm_convert_lead_wizard`'s guard is now a plain exclusive gateway: the
redundant `config.conditions` is gone and `e3b` carries `isDefault: true`. One
mechanism per decision, and exactly one branch runs.

Verified: 11 new engine/executor tests (including the reported repro in both
directions), 12 new linter tests; `@objectstack/service-automation` 577 tests
and `@objectstack/cli` 652 tests green, all three example apps build with no new
findings.

- Updated dependencies [80334c7]
- Updated dependencies [a7163ea]
- Updated dependencies [e6e9379]
- Updated dependencies [e6b1b69]
- Updated dependencies [2826d1e]
- Updated dependencies [5a84d41]
- Updated dependencies [20b1a9e]
- Updated dependencies [4820f55]
- Updated dependencies [462d9c4]
- Updated dependencies [5b843fb]
- Updated dependencies [b4487aa]
- Updated dependencies [67bf2e2]
- Updated dependencies [6117f7b]
- Updated dependencies [cdf4d9a]
- Updated dependencies [63b33e6]
- Updated dependencies [9ca2d85]
- Updated dependencies [5293114]
- Updated dependencies [ff17642]
- Updated dependencies [20bc357]
- Updated dependencies [3c7bcc0]
- Updated dependencies [4b6cac7]
- Updated dependencies [7631964]
- Updated dependencies [ac471a0]
- Updated dependencies [60ae58e]
- Updated dependencies [ce92674]
- Updated dependencies [07a4e26]
- Updated dependencies [ec975f1]
- Updated dependencies [eb4204b]
- Updated dependencies [4f13be2]
- Updated dependencies [ce92674]
- Updated dependencies [cf2c9b7]
- Updated dependencies [0f9faa2]
- Updated dependencies [7cf42fe]
- Updated dependencies [8aacf94]
- Updated dependencies [4638aaa]
- Updated dependencies [0222d3c]
- Updated dependencies [7bba90b]
- Updated dependencies [061406d]
- Updated dependencies [9c93465]
- Updated dependencies [ebb209c]
- Updated dependencies [63b33e6]
- Updated dependencies [2a44c1d]
- Updated dependencies [f3141d8]
- Updated dependencies [5a84d41]
- Updated dependencies [fd3013a]
- Updated dependencies [e5e7ee0]
- Updated dependencies [800bdb0]
- Updated dependencies [04f1182]
- Updated dependencies [38f7e4f]
- Updated dependencies [c57f3cf]
- Updated dependencies [97faca3]
- Updated dependencies [ad5fe25]
- Updated dependencies [ea90179]
- Updated dependencies [ce92674]
- Updated dependencies [5ef0b5b]
- Updated dependencies [48fbacb]
- Updated dependencies [355e951]
- Updated dependencies [dadb43f]
- @objectstack/spec@17.0.0-rc.2
- @objectstack/runtime@17.0.0-rc.2

## 4.0.92-rc.1

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion examples/app-crm/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@objectstack/example-crm",
"version": "4.0.92-rc.1",
"version": "4.0.92-rc.2",
"description": "Minimal CRM example — a smoke-test workspace that exercises the metadata loading pipeline (objects → views → app → dashboard → hook → flow → seed). For a full-featured enterprise CRM see https://github.com/objectstack-ai/hotcrm.",
"license": "Apache-2.0",
"private": true,
Expand Down
128 changes: 128 additions & 0 deletions examples/app-showcase/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,133 @@
# @objectstack/example-showcase

## 0.3.14-rc.2

### Patch Changes

- d449b0c: fix(cli): gate the two decision-routing shapes that can never work, and flag the inert `config.condition` (#4414)

Two follow-ups to #4440, both about metadata that reads like a guard and is not
one.

## Two rules promoted to `error`

`flow-branch-label-unmatched` and `flow-default-edge-with-condition` now FAIL the
build instead of warning. The bar for that — restated at the top of
`lint-flow-patterns.ts`, because the old one no longer described the set — is
**no reading of the author's metadata does what it says, deterministically, on
every run**. Both qualify: a branch label no out-edge carries cannot route, and
an edge that is both `isDefault` and conditional always lets the condition win,
so the marker routes nothing. Neither _fails_; both are wrong every time and
silently, which is worse.

The other two stay advisory on purpose, and the policy now says why:
`flow-decision-unconditional-branch` is usually a guard that does not guard, but
one guarded plus one unconditional out-edge is also a legal "maybe notify,
always continue" fan-out, and `flow-multiple-default-edges` can genuinely mean
"when nothing matched, do both". The bar is about _provability_, not severity of
consequence — failing a customer's build on a shape we cannot prove wrong is the
worse trade.

No wiring change was needed: `lintFlowPatterns` is already registered as
`tier: 'gating'` across all three commands (#4409), which is exactly the seam
`authoring-rule-wiring.test.ts` exists to guard.

## New rule: `flow-inert-node-condition`

`config.condition` is the trigger gate on a `start` node and is read by **no
other node type** — the engine parse-validates it everywhere (so a malformed one
is caught) and then ignores it. On a `decision` the name makes it read as the
branch predicate, which is exactly how it got authored.

Three of the three bundled apps had one. `app-todo`'s `check_recurring` and
`app-showcase`'s `needs_exec` both carried a predicate their out-edges were
already enforcing — a third copy doing nothing. The showcase even had a comment
next to it saying the node condition "is not evaluated by the engine", and kept
it anyway; that is the residue this rule exists to stop accumulating. Both are
now plain exclusive gateways.

Advisory, not gating: the surrounding edges usually still route correctly, so
this is dead weight rather than a provable misroute. The node-type list is a
closed set of builtins we have actually read, not "everything that isn't
`start`" — ADR-0018 keeps `node.type` open and a plugin executor may legitimately
declare and read its own `config.condition`.

## Studio

`objectstack-ai/objectui` carries the matching help-text fixes: the branch editor
said a `true` branch **is** the default/else path (it is how you _ask_ for one —
the marker goes on the out-edge), and the legacy single `Condition` field said
"prefer Branches above", which reads as "this works, but the other is better".
It does not work at all.

- Updated dependencies [80334c7]
- Updated dependencies [a7163ea]
- Updated dependencies [e6e9379]
- Updated dependencies [e6b1b69]
- Updated dependencies [2826d1e]
- Updated dependencies [5a84d41]
- Updated dependencies [20b1a9e]
- Updated dependencies [4820f55]
- Updated dependencies [462d9c4]
- Updated dependencies [5b843fb]
- Updated dependencies [b4487aa]
- Updated dependencies [67bf2e2]
- Updated dependencies [6117f7b]
- Updated dependencies [cdf4d9a]
- Updated dependencies [63b33e6]
- Updated dependencies [9ca2d85]
- Updated dependencies [5293114]
- Updated dependencies [ff17642]
- Updated dependencies [20bc357]
- Updated dependencies [3c7bcc0]
- Updated dependencies [4b6cac7]
- Updated dependencies [7631964]
- Updated dependencies [ac471a0]
- Updated dependencies [60ae58e]
- Updated dependencies [ce92674]
- Updated dependencies [07a4e26]
- Updated dependencies [ec975f1]
- Updated dependencies [eb4204b]
- Updated dependencies [4f13be2]
- Updated dependencies [ce92674]
- Updated dependencies [cf2c9b7]
- Updated dependencies [0f9faa2]
- Updated dependencies [7cf42fe]
- Updated dependencies [8aacf94]
- Updated dependencies [4638aaa]
- Updated dependencies [0222d3c]
- Updated dependencies [7bba90b]
- Updated dependencies [061406d]
- Updated dependencies [9c93465]
- Updated dependencies [ebb209c]
- Updated dependencies [63b33e6]
- Updated dependencies [2a44c1d]
- Updated dependencies [f3141d8]
- Updated dependencies [5a84d41]
- Updated dependencies [fd3013a]
- Updated dependencies [e5e7ee0]
- Updated dependencies [800bdb0]
- Updated dependencies [04f1182]
- Updated dependencies [38f7e4f]
- Updated dependencies [c57f3cf]
- Updated dependencies [97faca3]
- Updated dependencies [ad5fe25]
- Updated dependencies [ea90179]
- Updated dependencies [ce92674]
- Updated dependencies [5ef0b5b]
- Updated dependencies [48fbacb]
- Updated dependencies [355e951]
- Updated dependencies [dadb43f]
- @objectstack/spec@17.0.0-rc.2
- @objectstack/runtime@17.0.0-rc.2
- @objectstack/service-datasource@17.0.0-rc.2
- @objectstack/driver-sql@17.0.0-rc.2
- @objectstack/cloud-connection@17.0.0-rc.2
- @objectstack/connector-mcp@17.0.0-rc.2
- @objectstack/connector-openapi@17.0.0-rc.2
- @objectstack/connector-rest@17.0.0-rc.2
- @objectstack/connector-slack@17.0.0-rc.2

## 0.3.14-rc.1

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion examples/app-showcase/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@objectstack/example-showcase",
"version": "0.3.14-rc.1",
"version": "0.3.14-rc.2",
"description": "Kitchen-sink showcase workspace — exercises every metadata type, every view type, every chart type, and the major end-to-end capability chains (security, automation, analytics). Built for demonstration, debugging, and coverage-driven verification.",
"license": "Apache-2.0",
"private": true,
Expand Down
Loading