diff --git a/.gitignore b/.gitignore index 7e83257..9052899 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,7 @@ playwright-report/ # Local Claude Code tooling/session state .claude/ + +# Installed AI skills (skills.sh) +.agents/ +skills-lock.json diff --git a/packages/compliance/objectstack.config.ts b/packages/compliance/objectstack.config.ts index a56f38b..9f510d3 100644 --- a/packages/compliance/objectstack.config.ts +++ b/packages/compliance/objectstack.config.ts @@ -24,7 +24,7 @@ export default defineStack({ description: 'Starter template — compliance posture & evidence management on ObjectStack.', }, - requires: ['automation', 'analytics', 'auth', 'ui', 'sharing'], + requires: ['automation', 'triggers', 'analytics', 'auth', 'ui', 'sharing'], objects: Object.values(objects), views: Object.values(views), diff --git a/packages/compliance/src/flows/evidence_auto_expire.flow.ts b/packages/compliance/src/flows/evidence_auto_expire.flow.ts index e2d1bb6..c7323e3 100644 --- a/packages/compliance/src/flows/evidence_auto_expire.flow.ts +++ b/packages/compliance/src/flows/evidence_auto_expire.flow.ts @@ -22,8 +22,8 @@ export const EvidenceAutoExpireFlow: Flow = { label: 'Start', config: { objectName: 'compliance_evidence', - criteria: 'status == "approved" && expires_on != null && expires_on == daysAgo(0)', - criteriaDialect: 'cel', + triggerType: 'record-after-update', + condition: 'status == "approved" && expires_on != null && expires_on == daysAgo(0)', }, }, { @@ -32,8 +32,8 @@ export const EvidenceAutoExpireFlow: Flow = { label: 'Mark Expired', config: { objectName: 'compliance_evidence', - recordId: '{evidenceId}', - values: { status: 'expired' }, + filter: { id: '{record.id}' }, + fields: { status: 'expired' }, }, }, { id: 'end', type: 'end', label: 'End' }, diff --git a/packages/compliance/src/flows/evidence_expiring.flow.ts b/packages/compliance/src/flows/evidence_expiring.flow.ts index f987c5f..0fc1f59 100644 --- a/packages/compliance/src/flows/evidence_expiring.flow.ts +++ b/packages/compliance/src/flows/evidence_expiring.flow.ts @@ -23,9 +23,9 @@ export const EvidenceExpiringFlow: Flow = { label: 'Start', config: { objectName: 'compliance_evidence', - criteria: + triggerType: 'record-after-update', + condition: 'status == "approved" && expires_on != null && (expires_on == daysFromNow(30) || expires_on == daysFromNow(7))', - criteriaDialect: 'cel', }, }, { @@ -34,7 +34,7 @@ export const EvidenceExpiringFlow: Flow = { label: 'Load Evidence', config: { objectName: 'compliance_evidence', - filter: { id: '{evidenceId}' }, + filter: { id: '{record.id}' }, outputVariable: 'ev', }, }, diff --git a/packages/compliance/src/flows/failed_control_escalation.flow.ts b/packages/compliance/src/flows/failed_control_escalation.flow.ts index cd14076..cae264c 100644 --- a/packages/compliance/src/flows/failed_control_escalation.flow.ts +++ b/packages/compliance/src/flows/failed_control_escalation.flow.ts @@ -24,8 +24,8 @@ export const FailedControlEscalationFlow: Flow = { label: 'Start', config: { objectName: 'compliance_assessment', - criteria: 'status == "failed"', - criteriaDialect: 'cel', + triggerType: 'record-after-update', + condition: 'status == "failed"', }, }, { @@ -34,7 +34,7 @@ export const FailedControlEscalationFlow: Flow = { label: 'Load Assessment', config: { objectName: 'compliance_assessment', - filter: { id: '{assessmentId}' }, + filter: { id: '{record.id}' }, outputVariable: 'asmt', }, }, diff --git a/packages/content/objectstack.config.ts b/packages/content/objectstack.config.ts index 571d370..4e2dca8 100644 --- a/packages/content/objectstack.config.ts +++ b/packages/content/objectstack.config.ts @@ -27,7 +27,7 @@ export default defineStack({ // Opt-in capabilities. Foundational services (queue/job/cache/settings/ // email/storage) are auto-injected by the CLI; we only list the extras. - requires: ['automation', 'analytics', 'auth', 'ui', 'sharing'], + requires: ['automation', 'triggers', 'analytics', 'auth', 'ui', 'sharing'], objects: Object.values(objects), views: Object.values(views), diff --git a/packages/content/src/flows/cta_creation_default.flow.ts b/packages/content/src/flows/cta_creation_default.flow.ts index 0c26957..ac37ea8 100644 --- a/packages/content/src/flows/cta_creation_default.flow.ts +++ b/packages/content/src/flows/cta_creation_default.flow.ts @@ -27,7 +27,8 @@ export const CtaCreationDefaultFlow: Flow = { label: 'Start', config: { objectName: 'content_piece', - criteria: 'target_channels != NULL AND ISNEW()', + triggerType: 'record-after-create', + condition: '!isBlank(record.target_channels)', }, }, { @@ -36,7 +37,7 @@ export const CtaCreationDefaultFlow: Flow = { label: 'Load Piece', config: { objectName: 'content_piece', - filter: { id: '{pieceId}' }, + filter: { id: '{record.id}' }, outputVariable: 'piece', }, }, @@ -56,7 +57,7 @@ export const CtaCreationDefaultFlow: Flow = { label: 'Create Default CTA', config: { objectName: 'content_cta', - values: { + fields: { piece: '{piece.id}', label_text: 'Get started', goal: '{channel.default_cta_goal}', diff --git a/packages/content/src/flows/publication_rollup.flow.ts b/packages/content/src/flows/publication_rollup.flow.ts index 28cbdb1..95cb1ad 100644 --- a/packages/content/src/flows/publication_rollup.flow.ts +++ b/packages/content/src/flows/publication_rollup.flow.ts @@ -34,8 +34,9 @@ export const PublicationRollupFlow: Flow = { label: 'Start', config: { objectName: 'content_metric', - criteria: - 'ISCHANGED(views) OR ISCHANGED(clicks) OR ISCHANGED(signups) OR ISCHANGED(revenue) OR ISNEW()', + triggerType: 'record-after-update', + condition: + 'previous.views != record.views || previous.clicks != record.clicks || previous.signups != record.signups || previous.revenue != record.revenue', }, }, { @@ -44,7 +45,7 @@ export const PublicationRollupFlow: Flow = { label: 'Load Metric', config: { objectName: 'content_metric', - filter: { id: '{metricId}' }, + filter: { id: '{record.id}' }, outputVariable: 'metric', }, }, @@ -71,8 +72,8 @@ export const PublicationRollupFlow: Flow = { label: 'Update Publication Rollups', config: { objectName: 'content_publication', - recordId: '{metric.publication}', - values: { + filter: { id: '{metric.publication}' }, + fields: { total_views: '{pubTotals.views_sum}', total_clicks: '{pubTotals.clicks_sum}', total_signups: '{pubTotals.signups_sum}', @@ -113,8 +114,8 @@ export const PublicationRollupFlow: Flow = { label: 'Update Piece Rollups', config: { objectName: 'content_piece', - recordId: '{publication.piece}', - values: { + filter: { id: '{publication.piece}' }, + fields: { total_views: '{pieceTotals.views_sum}', total_clicks: '{pieceTotals.clicks_sum}', total_signups: '{pieceTotals.signups_sum}', diff --git a/packages/content/src/flows/publish_approval.flow.ts b/packages/content/src/flows/publish_approval.flow.ts index 4696425..852cfc1 100644 --- a/packages/content/src/flows/publish_approval.flow.ts +++ b/packages/content/src/flows/publish_approval.flow.ts @@ -32,8 +32,8 @@ export const PublishApprovalFlow: Flow = { label: 'Start', config: { objectName: 'content_piece', - criteria: 'status == "in_review"', - criteriaDialect: 'cel', + triggerType: 'record-after-update', + condition: 'status == "in_review"', }, }, { @@ -58,8 +58,8 @@ export const PublishApprovalFlow: Flow = { label: 'Mark Approved', config: { objectName: 'content_piece', - recordId: '{pieceId}', - values: { status: 'approved' }, + filter: { id: '{record.id}' }, + fields: { status: 'approved' }, }, }, { @@ -79,8 +79,8 @@ export const PublishApprovalFlow: Flow = { label: 'Send Back to Drafting', config: { objectName: 'content_piece', - recordId: '{pieceId}', - values: { status: 'drafting' }, + filter: { id: '{record.id}' }, + fields: { status: 'drafting' }, }, }, { diff --git a/packages/content/src/flows/signal_to_topic_promotion.flow.ts b/packages/content/src/flows/signal_to_topic_promotion.flow.ts index 7a56445..8c6640d 100644 --- a/packages/content/src/flows/signal_to_topic_promotion.flow.ts +++ b/packages/content/src/flows/signal_to_topic_promotion.flow.ts @@ -27,7 +27,8 @@ export const SignalToTopicPromotionFlow: Flow = { label: 'Start', config: { objectName: 'content_signal', - criteria: 'status == "promoted" && PRIOR(status) != "promoted"', + triggerType: 'record-after-update', + condition: 'status == "promoted" && PRIOR(status) != "promoted"', }, }, { @@ -36,7 +37,7 @@ export const SignalToTopicPromotionFlow: Flow = { label: 'Load Signal', config: { objectName: 'content_signal', - filter: { id: '{signalId}' }, + filter: { id: '{record.id}' }, outputVariable: 'signal', }, }, @@ -46,7 +47,7 @@ export const SignalToTopicPromotionFlow: Flow = { label: 'Create Topic', config: { objectName: 'content_topic', - values: { + fields: { title: '{signal.recommended_topic_title}', brief: 'Promoted from signal "{signal.headline}". Source: {signal.source_url}.\n\n{signal.summary}', @@ -65,8 +66,8 @@ export const SignalToTopicPromotionFlow: Flow = { label: 'Link Topic Back to Signal', config: { objectName: 'content_signal', - recordId: '{signal.id}', - values: { promoted_topic: '{topic.id}' }, + filter: { id: '{record.id}' }, + fields: { promoted_topic: '{topic.id}' }, }, }, { diff --git a/packages/content/src/flows/stamp_lifecycle_timestamps.flow.ts b/packages/content/src/flows/stamp_lifecycle_timestamps.flow.ts index 383f1c8..9e9e804 100644 --- a/packages/content/src/flows/stamp_lifecycle_timestamps.flow.ts +++ b/packages/content/src/flows/stamp_lifecycle_timestamps.flow.ts @@ -28,7 +28,8 @@ export const StampLifecycleTimestampsFlow: Flow = { label: 'Start', config: { objectName: 'content_piece', - criteria: 'ISCHANGED(status)', + triggerType: 'record-after-update', + condition: 'ISCHANGED(status)', }, }, { @@ -37,7 +38,7 @@ export const StampLifecycleTimestampsFlow: Flow = { label: 'Load Piece', config: { objectName: 'content_piece', - filter: { id: '{pieceId}' }, + filter: { id: '{record.id}' }, outputVariable: 'piece', }, }, diff --git a/packages/contracts/objectstack.config.ts b/packages/contracts/objectstack.config.ts index 588e212..31ddf00 100644 --- a/packages/contracts/objectstack.config.ts +++ b/packages/contracts/objectstack.config.ts @@ -21,13 +21,12 @@ export default defineStack({ version: '0.1.0', type: 'app', name: 'Contracts', - description: - 'Starter template — post-signature contract lifecycle management on ObjectStack.', + description: 'Starter template — post-signature contract lifecycle management on ObjectStack.', }, // Opt-in capabilities. Foundational services (queue/job/cache/settings/ // email/storage) are auto-injected by the CLI; we only list the extras. - requires: ['automation', 'analytics', 'auth', 'ui', 'sharing'], + requires: ['automation', 'triggers', 'analytics', 'auth', 'ui', 'sharing'], objects: Object.values(objects), views: Object.values(views), diff --git a/packages/contracts/src/flows/obligation_overdue.flow.ts b/packages/contracts/src/flows/obligation_overdue.flow.ts index eac4f14..02cfa0c 100644 --- a/packages/contracts/src/flows/obligation_overdue.flow.ts +++ b/packages/contracts/src/flows/obligation_overdue.flow.ts @@ -23,7 +23,8 @@ export const ObligationOverdueFlow: Flow = { label: 'Start', config: { objectName: 'contracts_obligation', - criteria: 'due_date < TODAY() AND status = "open"', + triggerType: 'record-after-update', + condition: 'due_date != null && due_date < daysFromNow(0) && status == "open"', }, }, { @@ -32,7 +33,7 @@ export const ObligationOverdueFlow: Flow = { label: 'Get Obligation', config: { objectName: 'contracts_obligation', - filter: { id: '{obligationId}' }, + filter: { id: '{record.id}' }, outputVariable: 'obligationRecord', }, }, diff --git a/packages/contracts/src/flows/renewal_alert.flow.ts b/packages/contracts/src/flows/renewal_alert.flow.ts index f9b1e75..c2706db 100644 --- a/packages/contracts/src/flows/renewal_alert.flow.ts +++ b/packages/contracts/src/flows/renewal_alert.flow.ts @@ -31,9 +31,9 @@ export const ContractRenewalAlertFlow: Flow = { label: 'Start', config: { objectName: 'contracts_contract', - criteria: + triggerType: 'record-after-update', + condition: 'status == "active" && end_date != null && (end_date == daysFromNow(60) || end_date == daysFromNow(30) || end_date == daysFromNow(7))', - criteriaDialect: 'cel', }, }, { @@ -42,7 +42,7 @@ export const ContractRenewalAlertFlow: Flow = { label: 'Get Contract', config: { objectName: 'contracts_contract', - filter: { id: '{contractId}' }, + filter: { id: '{record.id}' }, outputVariable: 'contractRecord', }, }, diff --git a/packages/contracts/src/flows/renewal_draft.flow.ts b/packages/contracts/src/flows/renewal_draft.flow.ts index 2fc3899..acd1c54 100644 --- a/packages/contracts/src/flows/renewal_draft.flow.ts +++ b/packages/contracts/src/flows/renewal_draft.flow.ts @@ -34,9 +34,9 @@ export const ContractRenewalDraftFlow: Flow = { label: 'Start', config: { objectName: 'contracts_contract', - criteria: + triggerType: 'record-after-update', + condition: 'status == "active" && auto_renew == false && end_date != null && renewal_notice_days != null && end_date == daysFromNow(renewal_notice_days)', - criteriaDialect: 'cel', }, }, { @@ -45,7 +45,7 @@ export const ContractRenewalDraftFlow: Flow = { label: 'Load Parent Contract', config: { objectName: 'contracts_contract', - filter: { id: '{contractId}' }, + filter: { id: '{record.id}' }, outputVariable: 'parent', }, }, @@ -55,7 +55,7 @@ export const ContractRenewalDraftFlow: Flow = { label: 'Create Renewal Draft', config: { objectName: 'contracts_contract', - values: { + fields: { title: 'Renewal — {parent.title}', party: '{parent.party}', contract_type: '{parent.contract_type}', diff --git a/packages/expense/objectstack.config.ts b/packages/expense/objectstack.config.ts index b81500a..bb21477 100644 --- a/packages/expense/objectstack.config.ts +++ b/packages/expense/objectstack.config.ts @@ -21,11 +21,10 @@ export default defineStack({ version: '0.1.0', type: 'app', name: 'Expense', - description: - 'Starter template — employee expense & reimbursement management on ObjectStack.', + description: 'Starter template — employee expense & reimbursement management on ObjectStack.', }, - requires: ['automation', 'analytics', 'auth', 'ui', 'sharing'], + requires: ['automation', 'triggers', 'analytics', 'auth', 'ui', 'sharing'], objects: Object.values(objects), views: Object.values(views), diff --git a/packages/expense/src/flows/expense_approval.flow.ts b/packages/expense/src/flows/expense_approval.flow.ts index e5b94c8..cd613d3 100644 --- a/packages/expense/src/flows/expense_approval.flow.ts +++ b/packages/expense/src/flows/expense_approval.flow.ts @@ -32,8 +32,8 @@ export const ExpenseApprovalFlow: Flow = { label: 'Start', config: { objectName: 'expense_report', - criteria: 'status == "submitted"', - criteriaDialect: 'cel', + triggerType: 'record-after-update', + condition: 'status == "submitted"', }, }, { @@ -58,8 +58,8 @@ export const ExpenseApprovalFlow: Flow = { label: 'Mark Approved', config: { objectName: 'expense_report', - recordId: '{reportId}', - values: { status: 'approved' }, + filter: { id: '{record.id}' }, + fields: { status: 'approved' }, }, }, { @@ -79,8 +79,8 @@ export const ExpenseApprovalFlow: Flow = { label: 'Mark Rejected', config: { objectName: 'expense_report', - recordId: '{reportId}', - values: { status: 'rejected' }, + filter: { id: '{record.id}' }, + fields: { status: 'rejected' }, }, }, { diff --git a/packages/expense/src/flows/expense_approval_overdue.flow.ts b/packages/expense/src/flows/expense_approval_overdue.flow.ts index 8548290..df02735 100644 --- a/packages/expense/src/flows/expense_approval_overdue.flow.ts +++ b/packages/expense/src/flows/expense_approval_overdue.flow.ts @@ -25,8 +25,8 @@ export const ExpenseApprovalOverdueFlow: Flow = { label: 'Start', config: { objectName: 'expense_report', - criteria: 'status == "submitted" && submitted_at != null && submitted_at == daysAgo(3)', - criteriaDialect: 'cel', + triggerType: 'record-after-update', + condition: 'status == "submitted" && submitted_at != null && submitted_at == daysAgo(3)', }, }, { @@ -35,7 +35,7 @@ export const ExpenseApprovalOverdueFlow: Flow = { label: 'Load Report', config: { objectName: 'expense_report', - filter: { id: '{reportId}' }, + filter: { id: '{record.id}' }, outputVariable: 'report', }, }, diff --git a/packages/expense/src/flows/expense_reimbursed.flow.ts b/packages/expense/src/flows/expense_reimbursed.flow.ts index 11486b1..e1d64b8 100644 --- a/packages/expense/src/flows/expense_reimbursed.flow.ts +++ b/packages/expense/src/flows/expense_reimbursed.flow.ts @@ -23,8 +23,8 @@ export const ExpenseReimbursedFlow: Flow = { label: 'Start', config: { objectName: 'expense_report', - criteria: 'status == "reimbursed"', - criteriaDialect: 'cel', + triggerType: 'record-after-update', + condition: 'status == "reimbursed"', }, }, { @@ -33,7 +33,7 @@ export const ExpenseReimbursedFlow: Flow = { label: 'Load Report', config: { objectName: 'expense_report', - filter: { id: '{reportId}' }, + filter: { id: '{record.id}' }, outputVariable: 'report', }, }, diff --git a/packages/expense/src/flows/expense_submitted.flow.ts b/packages/expense/src/flows/expense_submitted.flow.ts index 518c4b9..094add5 100644 --- a/packages/expense/src/flows/expense_submitted.flow.ts +++ b/packages/expense/src/flows/expense_submitted.flow.ts @@ -23,8 +23,8 @@ export const ExpenseSubmittedFlow: Flow = { label: 'Start', config: { objectName: 'expense_report', - criteria: 'status == "submitted"', - criteriaDialect: 'cel', + triggerType: 'record-after-update', + condition: 'status == "submitted"', }, }, { @@ -33,7 +33,7 @@ export const ExpenseSubmittedFlow: Flow = { label: 'Load Report', config: { objectName: 'expense_report', - filter: { id: '{reportId}' }, + filter: { id: '{record.id}' }, outputVariable: 'report', }, }, diff --git a/packages/helpdesk/objectstack.config.ts b/packages/helpdesk/objectstack.config.ts index 96f3fbc..b13fb18 100644 --- a/packages/helpdesk/objectstack.config.ts +++ b/packages/helpdesk/objectstack.config.ts @@ -30,7 +30,7 @@ export default defineStack({ 'AI-first customer support: native AI triage, suggested replies, KB recall, sentiment-driven escalation, customer portal via permissions.', }, - requires: ['automation', 'analytics', 'auth', 'ui', 'sharing'], + requires: ['automation', 'triggers', 'analytics', 'auth', 'ui', 'sharing'], objects: Object.values(objects), views: Object.values(views), diff --git a/packages/helpdesk/src/flows/ai_triage_on_create.flow.ts b/packages/helpdesk/src/flows/ai_triage_on_create.flow.ts index 59d5cc8..0ff3db2 100644 --- a/packages/helpdesk/src/flows/ai_triage_on_create.flow.ts +++ b/packages/helpdesk/src/flows/ai_triage_on_create.flow.ts @@ -42,9 +42,8 @@ export const AITriageOnCreateFlow: Flow = { label: 'Start', config: { objectName: 'helpdesk_ticket', - criteria: 'status == "new"', - criteriaDialect: 'cel', - triggerOn: 'create', + triggerType: 'record-after-create', + condition: 'status == "new"', }, }, { @@ -67,8 +66,8 @@ export const AITriageOnCreateFlow: Flow = { label: 'Advance Status → triaged', config: { objectName: 'helpdesk_ticket', - recordId: '{ticketId}', - values: { status: 'triaged' }, + filter: { id: '{record.id}' }, + fields: { status: 'triaged' }, }, }, { id: 'end', type: 'end', label: 'End' }, diff --git a/packages/helpdesk/src/flows/auto_close_resolved.flow.ts b/packages/helpdesk/src/flows/auto_close_resolved.flow.ts index 73bbdeb..9102ad4 100644 --- a/packages/helpdesk/src/flows/auto_close_resolved.flow.ts +++ b/packages/helpdesk/src/flows/auto_close_resolved.flow.ts @@ -26,8 +26,8 @@ export const AutoCloseResolvedFlow: Flow = { label: 'Start', config: { objectName: 'helpdesk_ticket', - criteria: 'status == "resolved" && resolved_at != null && resolved_at <= daysAgo(7)', - criteriaDialect: 'cel', + triggerType: 'record-after-update', + condition: 'status == "resolved" && resolved_at != null && resolved_at <= daysAgo(7)', }, }, { @@ -36,8 +36,8 @@ export const AutoCloseResolvedFlow: Flow = { label: 'Mark Closed', config: { objectName: 'helpdesk_ticket', - recordId: '{ticketId}', - values: { status: 'closed' }, + filter: { id: '{record.id}' }, + fields: { status: 'closed' }, }, }, { id: 'end', type: 'end', label: 'End' }, diff --git a/packages/helpdesk/src/flows/escalate_angry_customer.flow.ts b/packages/helpdesk/src/flows/escalate_angry_customer.flow.ts index 3ec729b..d8a829a 100644 --- a/packages/helpdesk/src/flows/escalate_angry_customer.flow.ts +++ b/packages/helpdesk/src/flows/escalate_angry_customer.flow.ts @@ -24,9 +24,9 @@ export const EscalateAngryCustomerFlow: Flow = { label: 'Start', config: { objectName: 'helpdesk_ticket', - criteria: + triggerType: 'record-after-update', + condition: 'ai_sentiment == "angry" && (priority == "high" || priority == "urgent") && status != "escalated" && status != "closed"', - criteriaDialect: 'cel', }, }, { @@ -35,7 +35,7 @@ export const EscalateAngryCustomerFlow: Flow = { label: 'Load Ticket', config: { objectName: 'helpdesk_ticket', - filter: { id: '{ticketId}' }, + filter: { id: '{record.id}' }, outputVariable: 'ticket', }, }, @@ -45,8 +45,8 @@ export const EscalateAngryCustomerFlow: Flow = { label: 'Mark Escalated', config: { objectName: 'helpdesk_ticket', - recordId: '{ticketId}', - values: { status: 'escalated' }, + filter: { id: '{record.id}' }, + fields: { status: 'escalated' }, }, }, { diff --git a/packages/helpdesk/src/flows/sla_first_response_warn.flow.ts b/packages/helpdesk/src/flows/sla_first_response_warn.flow.ts index f5461b7..f80ba6f 100644 --- a/packages/helpdesk/src/flows/sla_first_response_warn.flow.ts +++ b/packages/helpdesk/src/flows/sla_first_response_warn.flow.ts @@ -34,9 +34,9 @@ export const SlaFirstResponseWarnFlow: Flow = { label: 'Start', config: { objectName: 'helpdesk_ticket', - criteria: + triggerType: 'record-after-update', + condition: 'first_response_due_at != null && first_response_at == null && status != "closed" && status != "resolved"', - criteriaDialect: 'cel', }, }, { @@ -45,7 +45,7 @@ export const SlaFirstResponseWarnFlow: Flow = { label: 'Load Ticket', config: { objectName: 'helpdesk_ticket', - filter: { id: '{ticketId}' }, + filter: { id: '{record.id}' }, outputVariable: 'ticket', }, }, diff --git a/packages/helpdesk/src/flows/sla_resolution_breach.flow.ts b/packages/helpdesk/src/flows/sla_resolution_breach.flow.ts index f74f97d..8a12330 100644 --- a/packages/helpdesk/src/flows/sla_resolution_breach.flow.ts +++ b/packages/helpdesk/src/flows/sla_resolution_breach.flow.ts @@ -24,9 +24,9 @@ export const SlaResolutionBreachFlow: Flow = { label: 'Start', config: { objectName: 'helpdesk_ticket', - criteria: + triggerType: 'record-after-update', + condition: 'resolution_due_at != null && resolved_at == null && resolution_due_at < now() && status != "closed" && status != "escalated"', - criteriaDialect: 'cel', }, }, { @@ -35,7 +35,7 @@ export const SlaResolutionBreachFlow: Flow = { label: 'Load Ticket', config: { objectName: 'helpdesk_ticket', - filter: { id: '{ticketId}' }, + filter: { id: '{record.id}' }, outputVariable: 'ticket', }, }, @@ -45,8 +45,8 @@ export const SlaResolutionBreachFlow: Flow = { label: 'Mark Escalated', config: { objectName: 'helpdesk_ticket', - recordId: '{ticketId}', - values: { status: 'escalated', priority: 'urgent' }, + filter: { id: '{record.id}' }, + fields: { status: 'escalated', priority: 'urgent' }, }, }, { diff --git a/packages/hr/objectstack.config.ts b/packages/hr/objectstack.config.ts index 1829ffd..deadb55 100644 --- a/packages/hr/objectstack.config.ts +++ b/packages/hr/objectstack.config.ts @@ -21,12 +21,13 @@ export default defineStack({ version: '0.1.0', type: 'app', name: 'HR', - description: 'Starter template — people directory, time-off, and document expiry on ObjectStack.', + description: + 'Starter template — people directory, time-off, and document expiry on ObjectStack.', }, // Opt-in capabilities. Foundational services (queue/job/cache/settings/ // email/storage) are auto-injected by the CLI; we only list the extras. - requires: ['automation', 'analytics', 'auth', 'ui', 'sharing'], + requires: ['automation', 'triggers', 'analytics', 'auth', 'ui', 'sharing'], objects: Object.values(objects), views: Object.values(views), diff --git a/packages/hr/src/flows/document_expiring_soon.flow.ts b/packages/hr/src/flows/document_expiring_soon.flow.ts index 13c1430..e61f90c 100644 --- a/packages/hr/src/flows/document_expiring_soon.flow.ts +++ b/packages/hr/src/flows/document_expiring_soon.flow.ts @@ -27,7 +27,9 @@ export const DocumentExpiringSoonFlow: Flow = { label: 'Start', config: { objectName: 'hr_document', - criteria: 'expires_at != NULL AND expires_at >= TODAY() AND expires_at <= TODAY() + 30', + triggerType: 'record-after-update', + condition: + 'record.expires_at != null && record.expires_at >= today() && record.expires_at <= daysFromNow(30)', }, }, { @@ -36,7 +38,7 @@ export const DocumentExpiringSoonFlow: Flow = { label: 'Load Document', config: { objectName: 'hr_document', - filter: { id: '{docId}' }, + filter: { id: '{record.id}' }, outputVariable: 'doc', }, }, diff --git a/packages/hr/src/flows/time_off_submitted.flow.ts b/packages/hr/src/flows/time_off_submitted.flow.ts index a287646..39fd1b0 100644 --- a/packages/hr/src/flows/time_off_submitted.flow.ts +++ b/packages/hr/src/flows/time_off_submitted.flow.ts @@ -23,7 +23,8 @@ export const TimeOffSubmittedFlow: Flow = { label: 'Start', config: { objectName: 'hr_time_off_request', - criteria: 'status == "submitted" && PRIOR(status) != "submitted"', + triggerType: 'record-after-update', + condition: "record.status == 'submitted' && previous.status != 'submitted'", }, }, { @@ -32,7 +33,7 @@ export const TimeOffSubmittedFlow: Flow = { label: 'Load Request', config: { objectName: 'hr_time_off_request', - filter: { id: '{requestId}' }, + filter: { id: '{record.id}' }, outputVariable: 'req', }, }, diff --git a/packages/procurement/objectstack.config.ts b/packages/procurement/objectstack.config.ts index e34dcf3..f53328a 100644 --- a/packages/procurement/objectstack.config.ts +++ b/packages/procurement/objectstack.config.ts @@ -24,7 +24,7 @@ export default defineStack({ description: 'Starter template — source-to-pay procurement management on ObjectStack.', }, - requires: ['automation', 'analytics', 'auth', 'ui', 'sharing'], + requires: ['automation', 'triggers', 'analytics', 'auth', 'ui', 'sharing'], objects: Object.values(objects), views: Object.values(views), diff --git a/packages/procurement/src/flows/po_overdue.flow.ts b/packages/procurement/src/flows/po_overdue.flow.ts index 82427ca..f8d5825 100644 --- a/packages/procurement/src/flows/po_overdue.flow.ts +++ b/packages/procurement/src/flows/po_overdue.flow.ts @@ -24,9 +24,9 @@ export const POOverdueFlow: Flow = { label: 'Start', config: { objectName: 'procurement_order', - criteria: + triggerType: 'record-after-update', + condition: '(status == "sent" || status == "partial") && expected_delivery != null && expected_delivery == daysAgo(1)', - criteriaDialect: 'cel', }, }, { @@ -35,7 +35,7 @@ export const POOverdueFlow: Flow = { label: 'Load PO', config: { objectName: 'procurement_order', - filter: { id: '{poId}' }, + filter: { id: '{record.id}' }, outputVariable: 'po', }, }, diff --git a/packages/procurement/src/flows/pr_approval_required.flow.ts b/packages/procurement/src/flows/pr_approval_required.flow.ts index b784b9b..acab074 100644 --- a/packages/procurement/src/flows/pr_approval_required.flow.ts +++ b/packages/procurement/src/flows/pr_approval_required.flow.ts @@ -26,8 +26,8 @@ export const PRApprovalRequiredFlow: Flow = { label: 'Start', config: { objectName: 'procurement_request', - criteria: 'status == "submitted" && estimated_amount != null && estimated_amount >= 5000', - criteriaDialect: 'cel', + triggerType: 'record-after-update', + condition: 'status == "submitted" && estimated_amount != null && estimated_amount >= 5000', }, }, { @@ -36,7 +36,7 @@ export const PRApprovalRequiredFlow: Flow = { label: 'Load PR', config: { objectName: 'procurement_request', - filter: { id: '{prId}' }, + filter: { id: '{record.id}' }, outputVariable: 'pr', }, }, diff --git a/packages/procurement/src/flows/pr_to_po_convert.flow.ts b/packages/procurement/src/flows/pr_to_po_convert.flow.ts index 7a14969..edc4477 100644 --- a/packages/procurement/src/flows/pr_to_po_convert.flow.ts +++ b/packages/procurement/src/flows/pr_to_po_convert.flow.ts @@ -28,8 +28,8 @@ export const PRToPOConvertFlow: Flow = { label: 'Start', config: { objectName: 'procurement_request', - criteria: 'status == "approved" && converted_po == null', - criteriaDialect: 'cel', + triggerType: 'record-after-update', + condition: 'status == "approved" && converted_po == null', }, }, { @@ -38,7 +38,7 @@ export const PRToPOConvertFlow: Flow = { label: 'Load PR', config: { objectName: 'procurement_request', - filter: { id: '{prId}' }, + filter: { id: '{record.id}' }, outputVariable: 'pr', }, }, @@ -48,7 +48,7 @@ export const PRToPOConvertFlow: Flow = { label: 'Create Draft PO', config: { objectName: 'procurement_order', - values: { + fields: { po_number: 'PO-DRAFT-{pr.request_number}', vendor: '{pr.vendor}', source_request: '{pr.id}', @@ -70,8 +70,8 @@ export const PRToPOConvertFlow: Flow = { label: 'Mark PR Converted', config: { objectName: 'procurement_request', - recordId: '{pr.id}', - values: { status: 'converted', converted_po: '{po.id}' }, + filter: { id: '{record.id}' }, + fields: { status: 'converted', converted_po: '{po.id}' }, }, }, { diff --git a/packages/project/objectstack.config.ts b/packages/project/objectstack.config.ts index 3bbd4a3..9cbc223 100644 --- a/packages/project/objectstack.config.ts +++ b/packages/project/objectstack.config.ts @@ -14,10 +14,11 @@ export default defineStack({ version: '0.1.0', type: 'app', name: 'AI Project Management', - description: 'Project portfolio management with AI-powered risk prediction and resource optimization.', + description: + 'Project portfolio management with AI-powered risk prediction and resource optimization.', }, - requires: ['automation', 'analytics', 'auth', 'ui'], + requires: ['automation', 'triggers', 'job', 'analytics', 'auth', 'ui'], objects: Object.values(objects), views: Object.values(views), diff --git a/packages/project/src/flows/daily_ai_risk_assessment.flow.ts b/packages/project/src/flows/daily_ai_risk_assessment.flow.ts index e9df793..a5ebf76 100644 --- a/packages/project/src/flows/daily_ai_risk_assessment.flow.ts +++ b/packages/project/src/flows/daily_ai_risk_assessment.flow.ts @@ -78,8 +78,8 @@ export const DailyAIRiskAssessmentFlow: Flow = { label: 'Update Project AI Fields', config: { objectName: 'pm_project', - recordId: '{project.id}', - values: { + filter: { id: '{project.id}' }, + fields: { ai_last_prediction_at: '{now()}', // Other ai_* fields set by the stub/LLM function }, diff --git a/packages/project/src/flows/milestone_deadline_warning.flow.ts b/packages/project/src/flows/milestone_deadline_warning.flow.ts index e4cee77..e2655a8 100644 --- a/packages/project/src/flows/milestone_deadline_warning.flow.ts +++ b/packages/project/src/flows/milestone_deadline_warning.flow.ts @@ -1,6 +1,7 @@ // Copyright (c) 2026 ObjectStack contributors. Apache-2.0 license. import type * as Automation from '@objectstack/spec/automation'; +import { cel } from '@objectstack/spec'; type Flow = Automation.Flow; /** @@ -57,8 +58,13 @@ export const MilestoneDeadlineWarningFlow: Flow = { label: 'Mark Overdue as Missed', config: { objectName: 'pm_milestone', - recordIds: '{openMilestones.records[*].id}', - values: { + // Bulk-update: every still-open milestone whose planned date has passed. + // (update_record applies `fields` to all rows matching `filter`.) + filter: { + status: { $in: ['not_started', 'in_progress'] }, + planned_date: { $lt: cel`today()` }, + }, + fields: { status: 'missed', }, }, diff --git a/packages/project/src/flows/resource_conflict_detection.flow.ts b/packages/project/src/flows/resource_conflict_detection.flow.ts index cc9cc79..bbe9579 100644 --- a/packages/project/src/flows/resource_conflict_detection.flow.ts +++ b/packages/project/src/flows/resource_conflict_detection.flow.ts @@ -24,7 +24,7 @@ export const ResourceConflictDetectionFlow: Flow = { label: 'Start', config: { objectName: 'pm_resource', - triggerOn: 'create,update', + triggerType: 'record-after-update', }, }, { @@ -33,7 +33,8 @@ export const ResourceConflictDetectionFlow: Flow = { label: 'Get Resource Record', config: { objectName: 'pm_resource', - recordId: '{resourceId}', + filter: { id: '{record.id}' }, + outputVariable: 'resource', }, }, { diff --git a/packages/todo/objectstack.config.ts b/packages/todo/objectstack.config.ts index 72e816f..6d4c298 100644 --- a/packages/todo/objectstack.config.ts +++ b/packages/todo/objectstack.config.ts @@ -26,7 +26,7 @@ export default defineStack({ // Opt-in capabilities. Foundational services (queue/job/cache/settings/ // email/storage) are auto-injected by the CLI; we only list the extras. - requires: ['automation', 'analytics', 'auth', 'ui', 'sharing'], + requires: ['automation', 'triggers', 'analytics', 'auth', 'ui', 'sharing'], objects: Object.values(objects), views: Object.values(views), diff --git a/packages/todo/src/flows/task_assigned.flow.ts b/packages/todo/src/flows/task_assigned.flow.ts index 8fddac6..ba2242b 100644 --- a/packages/todo/src/flows/task_assigned.flow.ts +++ b/packages/todo/src/flows/task_assigned.flow.ts @@ -22,7 +22,8 @@ export const TaskAssignedFlow: Flow = { label: 'Start', config: { objectName: 'todo_task', - criteria: 'assignee != NULL AND assignee != PRIOR(assignee)', + triggerType: 'record-after-update', + condition: 'record.assignee != null && record.assignee != previous.assignee', }, }, { @@ -31,7 +32,7 @@ export const TaskAssignedFlow: Flow = { label: 'Get Task', config: { objectName: 'todo_task', - filter: { id: '{taskId}' }, + filter: { id: '{record.id}' }, outputVariable: 'taskRecord', }, }, diff --git a/packages/todo/src/flows/task_overdue.flow.ts b/packages/todo/src/flows/task_overdue.flow.ts index d6b43d1..fcedcd9 100644 --- a/packages/todo/src/flows/task_overdue.flow.ts +++ b/packages/todo/src/flows/task_overdue.flow.ts @@ -23,7 +23,8 @@ export const TaskOverdueFlow: Flow = { label: 'Start', config: { objectName: 'todo_task', - criteria: 'due_date < TODAY() AND status IN ("todo", "doing")', + triggerType: 'record-after-update', + condition: "record.due_date < today() && record.status in ['todo', 'doing']", }, }, { @@ -32,7 +33,7 @@ export const TaskOverdueFlow: Flow = { label: 'Get Task', config: { objectName: 'todo_task', - filter: { id: '{taskId}' }, + filter: { id: '{record.id}' }, outputVariable: 'taskRecord', }, },