From 4bae9d7d56f5cad7e83d31f8b4a62bf13fbcc5b3 Mon Sep 17 00:00:00 2001 From: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Date: Mon, 1 Jun 2026 22:23:58 +0800 Subject: [PATCH] feat(showcase): complete the state_machine validation demonstration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 3 status state machines (task/project/account) covered transition topologies but not the rule's other knobs. Round it out so the showcase exercises — and verifies — the full state_machine surface: - events: ['update'] on all three — state machines validate *transitions*, so they should run on update only (insert sets the initial state). Makes the intent explicit rather than relying on the insert short-circuit. - New advisory `project_health_progression` with severity: 'warning' — health should step one level at a time (green↔yellow↔red); a skip is flagged (logged) but NOT blocked, demonstrating soft guards alongside the existing error-severity (blocking) status flows. Verified against the runtime: error-severity invalid transition → HTTP 400 `invalid_transition`; warning-severity skip (health green→red) → HTTP 200. Co-Authored-By: Claude Opus 4.8 --- .../src/objects/account.object.ts | 2 ++ .../src/objects/project.object.ts | 22 +++++++++++++++++++ .../app-showcase/src/objects/task.object.ts | 2 ++ 3 files changed, 26 insertions(+) diff --git a/examples/app-showcase/src/objects/account.object.ts b/examples/app-showcase/src/objects/account.object.ts index 1ad83cdbf..52a35c4f8 100644 --- a/examples/app-showcase/src/objects/account.object.ts +++ b/examples/app-showcase/src/objects/account.object.ts @@ -48,6 +48,8 @@ export const Account = ObjectSchema.create({ label: 'Account Lifecycle', description: 'Accounts move prospect → active → churned, and can be reactivated.', field: 'status', + // Transitions are validated on update; insert sets the initial state. + events: ['update'] as const, message: 'Invalid account lifecycle transition.', transitions: { prospect: ['active', 'churned'], diff --git a/examples/app-showcase/src/objects/project.object.ts b/examples/app-showcase/src/objects/project.object.ts index 3193dfee7..069cf5a0a 100644 --- a/examples/app-showcase/src/objects/project.object.ts +++ b/examples/app-showcase/src/objects/project.object.ts @@ -74,6 +74,9 @@ export const Project = ObjectSchema.create({ label: 'Project Status Flow', description: 'Projects progress through valid status transitions.', field: 'status', + // State machines validate *transitions*, so they run on update only — + // an insert sets the initial state (constrained by the select options). + events: ['update'] as const, message: 'Invalid project status transition.', transitions: { planned: ['active', 'cancelled'], @@ -83,5 +86,24 @@ export const Project = ObjectSchema.create({ cancelled: ['planned'], }, }, + { + // Advisory (severity: 'warning') state machine — demonstrates a soft + // transition guard: health should escalate/de-escalate one step at a + // time (green↔yellow↔red). A jump (e.g. green→red) is *flagged* (logged + // server-side) but NOT blocked, unlike the error-severity status flow. + type: 'state_machine' as const, + name: 'project_health_progression', + label: 'Project Health Progression (advisory)', + description: 'Health should change one step at a time; skips are warned, not blocked.', + field: 'health', + events: ['update'] as const, + severity: 'warning' as const, + message: 'Health changed by more than one step — confirm this is intentional.', + transitions: { + green: ['yellow'], + yellow: ['green', 'red'], + red: ['yellow'], + }, + }, ], }); diff --git a/examples/app-showcase/src/objects/task.object.ts b/examples/app-showcase/src/objects/task.object.ts index 85eb544ba..927bc5b43 100644 --- a/examples/app-showcase/src/objects/task.object.ts +++ b/examples/app-showcase/src/objects/task.object.ts @@ -66,6 +66,8 @@ export const Task = ObjectSchema.create({ label: 'Task Status Flow', description: 'Tasks move forward through the board (reopen allowed from Done).', field: 'status', + // Transitions are validated on update; insert sets the initial state. + events: ['update'] as const, message: 'Invalid task status transition.', transitions: { backlog: ['todo'],