Skip to content

Commit bc44195

Browse files
xuyushun441-sysos-zhuangclaude
authored
chore(automation): retire the workflow_rule authoring paradigm (ADR-0018 M5 dropped) (#1578)
ADR-0019 dropped the Workflow-Rule -> Flow compiler, but the `workflow_rule` paradigm tag lingered in `ActionParadigmSchema` and on every built-in node descriptor. With no declarative Workflow-Rule authoring view to feed, retire it: - spec: `ActionParadigmSchema` -> `['flow','approval']` - service-automation: `http` / `notify` / `connector_action` descriptors and the deprecated-alias fallback advertise `['flow','approval']` - tests: builtin descriptor assertions + http-dispatcher `?paradigm` filter switched off the retired tag - docs: ADR-0018 status + migration table -> M3 shipped, M4 framework-complete, M5 dropped (approval convergence delivered by the ADR-0019 approval node) Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 7b129b8 commit bc44195

11 files changed

Lines changed: 36 additions & 16 deletions

File tree

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
'@objectstack/spec': patch
3+
'@objectstack/service-automation': patch
4+
---
5+
6+
chore(automation): retire the `workflow_rule` authoring paradigm (ADR-0018 M5 dropped)
7+
8+
ADR-0019 already removed the Workflow-Rule → Flow compiler (Workflow Rules were
9+
removed in #1398 and `workflow` was reclaimed for state machines), but the
10+
`workflow_rule` paradigm tag survived in `ActionParadigmSchema` and on every
11+
built-in node descriptor. There is no declarative Workflow-Rule authoring view
12+
to feed, so the tag is now retired: `ActionParadigmSchema` keeps `['flow',
13+
'approval']`, and the `http` / `notify` / `connector_action` descriptors (plus
14+
the deprecated-alias fallback) advertise `['flow', 'approval']`. Approval
15+
execution convergence is delivered by the ADR-0019 approval Flow node, not a
16+
compiler. ADR-0018's status and migration table are updated to mark M3 shipped,
17+
M4 framework-complete, and M5 dropped.

docs/adr/0018-unified-node-action-registry.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# ADR-0018: Unified Node/Action Registry across Flow, Workflow-Rule & Approval
22

3-
**Status**: Accepted (2026-05-31) — M1 + M2 implemented (built-in nodes folded into the core plugin; descriptor API + `GET /automation/actions` shipped). M3 (outbox-backed `http`/`notify`) next.
3+
**Status**: Accepted (2026-05-31) — M1–M3 implemented; M4 framework-side complete (designer config-forms tracked in `../objectui`); M5 **dropped** (Workflow-Rule→Flow compiler removed per [ADR-0019](./0019-approval-as-flow-node.md); approval execution converged via the approval Flow node, not a compiler). The `workflow_rule` authoring paradigm is retired.
44
**Deciders**: ObjectStack Protocol Architects
55
**Builds on**: [ADR-0005](./0005-metadata-customization-overlay.md) (one Zod source of truth per metadata type), [ADR-0012](./0012-notification-platform.md) (generalized outbox in `service-messaging`)
66
**Consumers**: `@objectstack/spec` (`automation/`), `@objectstack/services/service-automation`, `@objectstack/plugins/plugin-approvals`, `@objectstack/plugins/plugin-webhooks``service-messaging`, every plugin that registers a node executor, `../objectui` (`plugin-workflow` designer)
@@ -168,9 +168,9 @@ No fourth engine. Workflow Rules stays a **simplified authoring view** for busin
168168
|:---|:---|:---|
169169
| M1 ✅ | Add canonical `ActionDescriptorSchema` (+ `defineActionDescriptor`) alongside the legacy `NodeExecutorDescriptor`; `FlowNodeSchema.type` → validated `string` (with `FlowNodeAction`/`FLOW_BUILTIN_NODE_TYPES` retained as the seed set); `registerFlow()` soft-validates node types against the live registry (warn, don't hard-fail). | **Shipped.** Existing flows keep validating (built-in types seed-registered); plugin-registered node types are now legal flow nodes. |
170170
| M2 (partial ✅) | Built-in nodes (logic/crud/http/screen) publish descriptors and are **folded into the core `AutomationServicePlugin`** (seeded via `installBuiltinNodes()`), so `automation` is a self-contained capability — no companion node-pack plugins, no `extras` in the capability loader. `connector_action` dropped from the baseline (an integration concern needing a connector registry the platform doesn't ship; left to the integration layer / marketplace plugins via the still-open `registerNodeExecutor()`). Descriptors tagged `source: 'builtin'` vs `'plugin'`. `AutomationEngine.getActionDescriptors()`/`getActionDescriptor()` + optional `IAutomationService.getActionDescriptors()`. `GET /api/v1/automation/actions` shipped via `HttpDispatcher.handleAutomation` (`?paradigm`/`?source`/`?category` filters; `AutomationActionsResponseSchema` in spec; declarative entry in `DEFAULT_AUTOMATION_ROUTES`). | Additive; built-in flows keep working from the core plugin alone. Hosts that hand-assembled the four `*NodesPlugin` classes drop them (the classes are gone). |
171-
| M3 | Introduce `http` + `notify` executors backed by `service-messaging`; register `http_request`/`http_call`/`webhook` as deprecated aliases | Old node types keep running via alias. |
172-
| M4 | Designer palette + config forms driven by the registry; remove hardcoded `FlowNodeType` | Designer-only; old saved graphs still load. |
173-
| M5 | Workflow-Rule → Flow compiler; `plugin-approvals` action executor delegates to the shared `http`/`notify` executors | Approval/Workflow-Rule definitions unchanged; execution path converges. |
171+
| M3 | Canonical `http` + `notify` executors backed by `service-messaging` (HTTP outbox `sys_http_delivery` / notification outbox); `http_request`/`http_call`/`webhook` registered as **deprecated aliases** (`registerNodeAlias`, `needsOutbox`). | **Shipped.** Old node types keep running via alias; degrade to inline when no outbox is wired. |
172+
| M4 (framework ✅) | Hardcoded `FlowNodeType` removed (M1 turned `FlowNodeSchema.type` into a validated string seeded from `FLOW_BUILTIN_NODE_TYPES`); `GET /automation/actions` drives the palette. Remaining: designer config-forms rendered from `descriptor.configSchema` — tracked in `../objectui`. | Framework carries no closed node enum; old saved graphs still load. |
173+
| ~~M5~~ **Dropped** | Workflow-Rule → Flow compiler **removed** — there is no declarative Workflow-Rule authoring type to compile (Workflow Rules removed in #1398; `workflow` reclaimed for state machines, ADR-0020). The `workflow_rule` paradigm tag is retired from `ActionParadigmSchema` and all descriptors. Approval execution convergence is delivered by [ADR-0019](./0019-approval-as-flow-node.md) (approval is a durable-pause Flow node; side-effects run on the shared `http`/`notify` executors), not a compiler. | No legacy to migrate (greenfield). |
174174

175175
---
176176

@@ -231,6 +231,6 @@ Managed credentials/secret vault, OAuth2 token refresh, multi-tenant connection
231231
### Implementation checklist
232232

233233
- [x] `AutomationEngine`: connector registry (`registerConnector` / `unregisterConnector` / `resolveConnectorAction` / `getRegisteredConnectors`) + `ConnectorActionHandler` / `ConnectorActionContext` types.
234-
- [x] `builtin/connector-nodes.ts`: `connector_action` executor + descriptor (`category: 'io'`, `source: 'builtin'`, `paradigms: ['flow','workflow_rule','approval']`), wired into `installBuiltinNodes()`. The core plugin now seeds 11 baseline node types (was 10).
234+
- [x] `builtin/connector-nodes.ts`: `connector_action` executor + descriptor (`category: 'io'`, `source: 'builtin'`, `paradigms: ['flow','approval']`), wired into `installBuiltinNodes()`. The core plugin now seeds 11 baseline node types (was 10).
235235
- [x] First concrete plugin `@objectstack/connector-rest` (the reference connector) validating the registry — `request` action, static auth (`none`/`api-key`/`basic`/`bearer`), no OAuth2 refresh.
236236
- [x] Tests: baseline dispatch (fake connector) + REST plugin auth-header injection + end-to-end kernel boot (both plugins → `connector_action` flow → REST handler).

packages/runtime/src/http-dispatcher.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ describe('HttpDispatcher', () => {
147147
trigger: vi.fn().mockResolvedValue({ success: true }),
148148
getActionDescriptors: vi.fn().mockReturnValue([
149149
{ type: 'decision', name: 'Decision', category: 'logic', paradigms: ['flow'], source: 'builtin' },
150-
{ type: 'http_request', name: 'HTTP Request', category: 'io', paradigms: ['flow', 'workflow_rule'], source: 'builtin' },
150+
{ type: 'http_request', name: 'HTTP Request', category: 'io', paradigms: ['flow', 'approval'], source: 'builtin' },
151151
{ type: 'send_sms', name: 'Send SMS', category: 'io', paradigms: ['flow'], source: 'plugin' },
152152
]),
153153
getConnectorDescriptors: vi.fn().mockReturnValue([
@@ -270,7 +270,7 @@ describe('HttpDispatcher', () => {
270270
});
271271

272272
it('should filter GET /actions by ?paradigm', async () => {
273-
const result = await dispatcher.handleAutomation('actions', 'GET', {}, { request: {} }, { paradigm: 'workflow_rule' });
273+
const result = await dispatcher.handleAutomation('actions', 'GET', {}, { request: {} }, { paradigm: 'approval' });
274274
expect(result.handled).toBe(true);
275275
expect(result.response?.body?.data?.total).toBe(1);
276276
expect(result.response?.body?.data?.actions[0].type).toBe('http_request');

packages/services/service-automation/src/builtin/connector-nodes.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ describe('connector_action (baseline node)', () => {
5151
expect(descriptor?.source).toBe('builtin');
5252
expect(descriptor?.category).toBe('io');
5353
expect(descriptor?.paradigms).toEqual(
54-
expect.arrayContaining(['flow', 'workflow_rule', 'approval']),
54+
expect.arrayContaining(['flow', 'approval']),
5555
);
5656
});
5757

packages/services/service-automation/src/builtin/connector-nodes.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@ export function registerConnectorNodes(engine: AutomationEngine, ctx: PluginCont
3232
category: 'io',
3333
source: 'builtin',
3434
supportsRetry: true,
35-
// Present in all three authoring paradigms (ADR-0018 §registry table).
36-
paradigms: ['flow', 'workflow_rule', 'approval'],
35+
// Present in both authoring paradigms (ADR-0018 §registry table;
36+
// workflow_rule retired per ADR-0019).
37+
paradigms: ['flow', 'approval'],
3738
// Config contract — drives the Studio property form and flow validation.
3839
configSchema: {
3940
type: 'object',

packages/services/service-automation/src/builtin/http-nodes.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ describe('http (canonical node) + deprecated aliases', () => {
5858
expect(d?.source).toBe('builtin');
5959
expect(d?.category).toBe('io');
6060
expect(d?.needsOutbox).toBe(true);
61-
expect(d?.paradigms).toEqual(expect.arrayContaining(['flow', 'workflow_rule', 'approval']));
61+
expect(d?.paradigms).toEqual(expect.arrayContaining(['flow', 'approval']));
6262
});
6363

6464
it('registers http_request/http_call/webhook as deprecated aliases of http', () => {

packages/services/service-automation/src/builtin/http-nodes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export function registerHttpNodes(engine: AutomationEngine, ctx: PluginContext):
7373
// and the messaging HTTP outbox is wired).
7474
needsOutbox: true,
7575
supportsRetry: true,
76-
paradigms: ['flow', 'workflow_rule', 'approval'],
76+
paradigms: ['flow', 'approval'],
7777
configSchema: {
7878
type: 'object',
7979
required: ['url'],

packages/services/service-automation/src/builtin/notify-node.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ describe('notify (baseline node)', () => {
7272
expect(descriptor?.source).toBe('builtin');
7373
expect(descriptor?.category).toBe('io');
7474
expect(descriptor?.paradigms).toEqual(
75-
expect.arrayContaining(['flow', 'workflow_rule', 'approval']),
75+
expect.arrayContaining(['flow', 'approval']),
7676
);
7777
});
7878

packages/services/service-automation/src/builtin/notify-node.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export function registerNotifyNode(engine: AutomationEngine, ctx: PluginContext)
6767
// Delivery is outbox-backed inside the messaging service (ADR-0030
6868
// emit → sys_notification_delivery), so it inherits retry/dead-letter.
6969
needsOutbox: true,
70-
paradigms: ['flow', 'workflow_rule', 'approval'],
70+
paradigms: ['flow', 'approval'],
7171
}),
7272
async execute(node, variables, context) {
7373
const cfg = (node.config ?? {}) as Record<string, unknown>;

packages/services/service-automation/src/engine.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -450,7 +450,7 @@ export class AutomationEngine implements IAutomationService {
450450
description: `Deprecated alias of '${canonicalType}' (ADR-0018 M3). Author new flows with '${canonicalType}'.`,
451451
category: meta?.category ?? 'io',
452452
source: 'builtin',
453-
paradigms: meta?.paradigms ?? ['flow', 'workflow_rule', 'approval'],
453+
paradigms: meta?.paradigms ?? ['flow', 'approval'],
454454
supportsRetry: true,
455455
needsOutbox: meta?.needsOutbox ?? false,
456456
deprecated: true,

0 commit comments

Comments
 (0)