Skip to content

Commit 1c80d9d

Browse files
committed
examples(showcase): demonstrate quorum, time-relative trigger, and initialStates
Adds coverage for three shipped 16.0 features the showcase was missing (all shapes verified against @objectstack/spec schemas; each mirrors an existing flow in the same file): - CommitteeQuorumFlow — a behavior:'quorum' approval requiring any 2-of-3 committee positions (manager/finance/legal) on a high-value expense report (#3266 M-of-N), the collective-tally complement to ExpenseSignoffFlow's per_group 会签. - TaskDueReminderFlow — a type:'schedule' flow whose start node declares config.timeRelative (showcase_task.due_date, offsetDays:[3,1], filter status != done) to remind owners before a task is due (#1874). - project_status_flow — adds initialStates:['planned'] (+ 'insert' in events) so a project can only be created in 'planned', not born mid-flow (#3165). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AHVoqyH9VAjmbyMN1Ri2jf
1 parent f8649e9 commit 1c80d9d

2 files changed

Lines changed: 112 additions & 3 deletions

File tree

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

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1430,9 +1430,115 @@ export const ExpenseSignoffFlow = defineFlow({
14301430
],
14311431
});
14321432

1433+
/**
1434+
* Committee Quorum (#3266) — a `quorum` (M-of-N) approval, the collective
1435+
* sign-off complement to {@link ExpenseSignoffFlow}'s per-group 会签. A
1436+
* high-value expense report needs **any 2 of 3** committee members (manager /
1437+
* finance / legal) to approve; a single rejection still vetoes, and
1438+
* `minApprovals` clamps to the approver count so a misconfiguration can never
1439+
* deadlock. Where `per_group` requires one from EACH group, `quorum` counts a
1440+
* flat tally across all approvers.
1441+
*/
1442+
export const CommitteeQuorumFlow = defineFlow({
1443+
name: 'showcase_committee_quorum',
1444+
label: 'High-Value Expense — Committee Quorum',
1445+
description: 'Any 2-of-3 committee members approve a high-value expense report (#3266 quorum / M-of-N).',
1446+
type: 'autolaunched',
1447+
status: 'active',
1448+
nodes: [
1449+
{
1450+
id: 'start',
1451+
type: 'start',
1452+
label: 'On High-Value Submitted',
1453+
config: {
1454+
objectName: 'showcase_expense_report',
1455+
triggerType: 'record-after-update',
1456+
condition: 'status == "submitted" && previous.status != "submitted" && total_amount >= 5000',
1457+
},
1458+
},
1459+
{
1460+
id: 'committee',
1461+
type: 'approval',
1462+
label: 'Committee Sign-off (2 of 3)',
1463+
config: {
1464+
approvers: [
1465+
{ type: 'position', value: 'manager' },
1466+
{ type: 'position', value: 'finance' },
1467+
{ type: 'position', value: 'legal' },
1468+
],
1469+
behavior: 'quorum',
1470+
minApprovals: 2,
1471+
lockRecord: true,
1472+
},
1473+
},
1474+
{ id: 'approved', type: 'end', label: 'Approved' },
1475+
{ id: 'rejected', type: 'end', label: 'Rejected' },
1476+
],
1477+
edges: [
1478+
{ id: 'e1', source: 'start', target: 'committee' },
1479+
{ id: 'e2', source: 'committee', target: 'approved', label: 'approve' },
1480+
{ id: 'e3', source: 'committee', target: 'rejected', label: 'reject' },
1481+
],
1482+
});
1483+
1484+
/**
1485+
* Task Due Reminder (#1874) — a declarative `timeRelative` sweep, far more
1486+
* robust than a `record_change` flow gated on `due_date == daysFromNow(n)`
1487+
* (which fires only if the task happens to be edited on the exact threshold
1488+
* day). A daily sweep launches this flow **once per matching task** at T-minus
1489+
* 3 and 1 days before its `due_date`, with the task on the flow context. Swap
1490+
* `offsetDays` for `withinDays: 7` to nudge everything due within a week
1491+
* (negative = overdue lookback).
1492+
*/
1493+
export const TaskDueReminderFlow = defineFlow({
1494+
name: 'showcase_task_due_reminder',
1495+
label: 'Task Due Reminder',
1496+
description: 'Daily sweep: remind the owner 3 and 1 days before an open task is due (#1874 time-relative).',
1497+
type: 'schedule',
1498+
status: 'active',
1499+
runAs: 'system', // a sweep has no trigger user — elevate explicitly
1500+
nodes: [
1501+
{
1502+
id: 'start',
1503+
type: 'start',
1504+
label: 'Daily Sweep',
1505+
config: {
1506+
timeRelative: {
1507+
object: 'showcase_task',
1508+
dateField: 'due_date',
1509+
offsetDays: [3, 1], // — or — withinDays: 7 (negative = overdue lookback)
1510+
filter: { status: { $ne: 'done' } }, // optional, ANDed with the date window
1511+
},
1512+
// schedule defaults to daily 08:00 UTC; override with
1513+
// schedule: { type: 'cron', expression: '0 8 * * *' }
1514+
},
1515+
},
1516+
{
1517+
id: 'remind_owner',
1518+
type: 'notify',
1519+
label: 'Remind Owner',
1520+
config: {
1521+
topic: 'task.due_soon',
1522+
channels: ['inbox'],
1523+
severity: 'warning',
1524+
title: 'Task due soon: {record.title}',
1525+
message: 'Your task "{record.title}" is due on {record.due_date}.',
1526+
actionUrl: '/showcase_task',
1527+
},
1528+
},
1529+
{ id: 'end', type: 'end', label: 'End' },
1530+
],
1531+
edges: [
1532+
{ id: 'e1', source: 'start', target: 'remind_owner' },
1533+
{ id: 'e2', source: 'remind_owner', target: 'end' },
1534+
],
1535+
});
1536+
14331537
export const allFlows = [
14341538
TaskCompletedFlow,
14351539
ExpenseSignoffFlow,
1540+
CommitteeQuorumFlow,
1541+
TaskDueReminderFlow,
14361542
ReassignWizardFlow,
14371543
InquiryPurgeFlow,
14381544
BudgetApprovalFlow,

examples/app-showcase/src/data/objects/project.object.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,12 @@ export const Project = ObjectSchema.create({
124124
label: 'Project Status Flow',
125125
description: 'Projects progress through valid status transitions.',
126126
field: 'status',
127-
// State machines validate *transitions*, so they run on update only —
128-
// an insert sets the initial state (constrained by the select options).
129-
events: ['update'] as const,
127+
// `transitions` govern UPDATE; `initialStates` (#3165) is the FSM entry
128+
// point on INSERT — without it a `select` would accept ANY option as the
129+
// initial value, so a project could be born already `completed`. Requiring
130+
// `insert` in `events` is what makes the initialStates check run on create.
131+
events: ['insert', 'update'] as const,
132+
initialStates: ['planned'],
130133
message: 'Invalid project status transition.',
131134
transitions: {
132135
planned: ['active', 'cancelled'],

0 commit comments

Comments
 (0)