Skip to content

Commit b857a7a

Browse files
os-zhuangclaude
andcommitted
feat(showcase): dynamic-approval demo flow — decision outputs feed the next stage's expression approvers (#3447)
The lead reviewer picks the co-signers in their decision (decisionOutputs); the co-sign node resolves them at entry from vars.lead_review.next_reviewers. Browser-dogfooded end to end: dialog renders the declared output field, the decide body nests outputs, and the co-sign slate resolves to the picked ids. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 33e3614 commit b857a7a

2 files changed

Lines changed: 71 additions & 0 deletions

File tree

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
import { defineFlow } from '@objectstack/spec';
4+
5+
/**
6+
* #3447 P2 dogfood: dynamic approver routing end to end.
7+
*
8+
* Two-stage approval where the FIRST approver picks the second stage's
9+
* approvers in their decision (`decisionOutputs`), and the second stage
10+
* resolves them at node entry from an `expression` approver over `vars.*` —
11+
* no record-field detour. Trigger: retitling an announcement (an otherwise
12+
* approval-free object, so this demo never collides with the expense/invoice/
13+
* project approval flows — the service dedupes pending requests per record).
14+
*/
15+
export const DynamicApprovalFlow = defineFlow({
16+
name: 'showcase_dynamic_approval',
17+
label: 'Announcement Dynamic Approval',
18+
description: 'Lead reviewer picks the co-signers in their decision; the co-sign step resolves them at entry.',
19+
type: 'autolaunched',
20+
status: 'active',
21+
nodes: [
22+
{
23+
id: 'start',
24+
type: 'start',
25+
label: 'On Announcement Retitled',
26+
config: {
27+
objectName: 'showcase_announcement',
28+
triggerType: 'record-after-update',
29+
condition: 'title != previous.title',
30+
},
31+
},
32+
{
33+
id: 'lead_review',
34+
type: 'approval',
35+
label: 'Lead Review',
36+
config: {
37+
// The seeded admin holds the org-membership `owner` tier, so the demo
38+
// routes stage 1 to them without hard-coding a user id.
39+
approvers: [{ type: 'org_membership_level', value: 'owner' }],
40+
behavior: 'first_response',
41+
lockRecord: true,
42+
// The lead hands the co-signers to the flow with their decision.
43+
decisionOutputs: ['next_reviewers'],
44+
},
45+
},
46+
{
47+
id: 'co_sign',
48+
type: 'approval',
49+
label: 'Co-sign',
50+
config: {
51+
// Resolved at node entry from the lead's decision outputs (#3447 P2).
52+
approvers: [{ type: 'expression', value: 'vars.lead_review.next_reviewers' }],
53+
behavior: 'unanimous',
54+
lockRecord: true,
55+
onEmptyApprovers: 'fail',
56+
},
57+
},
58+
{ id: 'approved', type: 'end', label: 'Approved' },
59+
{ id: 'rejected', type: 'end', label: 'Rejected' },
60+
],
61+
edges: [
62+
{ id: 'e1', source: 'start', target: 'lead_review' },
63+
{ id: 'e2', source: 'lead_review', target: 'co_sign', label: 'approve' },
64+
{ id: 'e3', source: 'lead_review', target: 'rejected', label: 'reject' },
65+
{ id: 'e4', source: 'co_sign', target: 'approved', label: 'approve' },
66+
{ id: 'e5', source: 'co_sign', target: 'rejected', label: 'reject' },
67+
],
68+
});

examples/app-showcase/src/automation/flows/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
22

33
import { defineFlow } from '@objectstack/spec';
4+
import { DynamicApprovalFlow } from './dynamic-approval.flow';
45

56
/**
67
* Task Completed → Notify — an autolaunched, record-triggered flow that fires
@@ -1620,4 +1621,6 @@ export const allFlows = [
16201621
ResilientSyncFlow,
16211622
ProjectEscalationFlow,
16221623
InboundTaskWebhookFlow,
1624+
// #3447 P2 dogfood: expression approvers + decision outputs, end to end.
1625+
DynamicApprovalFlow,
16231626
];

0 commit comments

Comments
 (0)