Skip to content

Commit 4705fb8

Browse files
xuyushun441-sysos-zhuangclaude
authored
fix(ai): solution_design no longer models a process/approval as a table (#1570)
Asking the assistant to "design expense reimbursement" made it invent an approval_record TABLE by default (process-as-data anti-pattern); it only used a flow after the user pushed back. Hardens the default: - propose_blueprint generation prompt: status = select field (never a table); do NOT create approval/approval_record/approval_step/workflow/process objects (a process is a flow; trail = platform history); approver/reviewer/owner are lookup fields; add an assumption that the approval flow is a separate step. - solution_design skill: same rules + proactively draft the approval flow after apply_blueprint (get_metadata_schema('flow') -> create_metadata type:flow, bound to the app package) instead of waiting to be asked; optional state_machine rule for transitions. Regression test asserts the guardrail in the skill instructions. service-ai suite green (480). Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 2227ea9 commit 4705fb8

4 files changed

Lines changed: 74 additions & 0 deletions

File tree

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
'@objectstack/service-ai': patch
3+
---
4+
5+
fix(ai): solution_design no longer models a process/approval as a table
6+
7+
Asking the assistant to "design expense reimbursement" made it, by default, invent an `approval_record` TABLE to represent the approval process — a non-functional "process-as-data" anti-pattern. It only switched to a flow after the user pushed back.
8+
9+
Hardens the default in two places:
10+
11+
- **`propose_blueprint` generation prompt** (the "metadata architect" system message): status/lifecycle is modeled as a `select` field, never a table; it must NOT create `approval` / `approval_record` / `approval_step` / `workflow` / `process` objects (a process is a flow, its trail comes from platform history); the people a process references (approver/reviewer/owner) are `lookup` fields; and if the goal implies a process it adds an assumption that the approval *flow* is a separate step.
12+
13+
- **`solution_design` skill instructions**: the same modeling rules, plus — when the goal involves a process — the agent now PROACTIVELY drafts the approval flow after `apply_blueprint` (call `get_metadata_schema('flow')`, then `create_metadata(type:'flow', …)` with the approval node(s), bound to the same app package) instead of waiting for the user to ask "now create the flow". Optionally adds a `state_machine` rule to block illegal status transitions.
14+
15+
Regression-tested: `solution-design-guardrail.test.ts` asserts the skill instructions carry the no-process-as-table rule, status-as-select, and the proactive-flow step.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
import { describe, it, expect } from 'vitest';
4+
import { SOLUTION_DESIGN_SKILL } from '../skills/solution-design-skill.js';
5+
6+
/**
7+
* Regression guard for the "don't model a process as data" rule. The default
8+
* behaviour was that asking the agent to "design expense reimbursement" made it
9+
* invent an `approval_record` TABLE instead of a flow. The fix lives in the
10+
* skill instructions (and the propose_blueprint generation prompt): status is a
11+
* select field, an approval process is a FLOW, and the agent proactively drafts
12+
* that flow instead of waiting to be asked. These assertions keep that guidance
13+
* from silently regressing.
14+
*/
15+
describe('solution_design — process/state guardrail', () => {
16+
const text = SOLUTION_DESIGN_SKILL.instructions.toLowerCase();
17+
18+
it('tells the agent NOT to model a process/approval as a table', () => {
19+
expect(text).toContain('do not model a process as data');
20+
expect(text).toMatch(/never create objects for approvals/);
21+
expect(text).toMatch(/is a flow, not a table/);
22+
});
23+
24+
it('tells the agent to model status as a select field', () => {
25+
expect(text).toMatch(/status\b.*\bselect\b|\bselect\b.*\bstatus/);
26+
});
27+
28+
it('tells the agent to proactively draft the approval flow (not wait to be asked)', () => {
29+
expect(text).toContain('get_metadata_schema');
30+
expect(text).toMatch(/create_metadata\(type:'flow'/);
31+
expect(text).toMatch(/do not wait for the user to ask/);
32+
});
33+
34+
it('still drives the plan-first propose -> apply flow', () => {
35+
expect(SOLUTION_DESIGN_SKILL.tools).toEqual(['propose_blueprint', 'apply_blueprint']);
36+
});
37+
});

packages/services/service-ai/src/skills/solution-design-skill.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,15 @@ Hard rules:
3030
- Seed data in a blueprint is a suggestion only; it is not auto-applied.
3131
- Always answer in the same language the user is using.
3232
33+
Process & state modeling — do NOT model a process as data:
34+
- Record STATUS / lifecycle is a \`select\` field on the main object (e.g. status = draft/submitted/approved/rejected/paid), never a separate table.
35+
- NEVER create objects for approvals, approval steps/records, workflows, routing, sign-offs, or audit trails. An approval/automation process is a FLOW, not a table; its trail comes from platform history. Model only the people the process references (approver/reviewer/owner) as lookup fields to the user object.
36+
- If the goal involves a process (approval / 审批 / review / routing / multi-step state transitions / sign-off / escalation), the blueprint covers only the DATA model. After apply_blueprint drafts it, PROACTIVELY draft the approval flow yourself — do NOT wait for the user to ask "now create the flow":
37+
1. Call get_metadata_schema('flow') to get the exact shape (it supports an \`approval\` node).
38+
2. Call create_metadata(type:'flow', ...) with the approval node(s) wired to the status field and approver lookups, binding it to the SAME app package (pass the packageId from apply_blueprint's result, or set_active_package first) so it is not orphaned.
39+
3. Optionally add a state_machine validation rule on the object so illegal status transitions are blocked.
40+
Then tell the user you have ALSO drafted the approval flow for their review (it is a draft, like everything else).
41+
3342
For small, specific changes ("add a status field to account") use the metadata_authoring tools directly instead of a blueprint.`,
3443
tools: [
3544
'propose_blueprint',

packages/services/service-ai/src/tools/blueprint-tools.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,19 @@ function createProposeBlueprintHandler(ctx: BlueprintToolContext): ToolHandler {
131131
'Rules:\n' +
132132
'- Use snake_case for every object, field, and view name.\n' +
133133
'- Prefer a small, sensible field set per object over an exhaustive one.\n' +
134+
'- Model record STATUS / lifecycle stage as a single `select` field on the ' +
135+
'main object (e.g. a `status` field with options like draft/submitted/approved/' +
136+
'rejected/paid), NOT as a separate table.\n' +
137+
'- A PROCESS is not data. Do NOT create objects for approvals, approval ' +
138+
'steps/records, workflows, routing, sign-offs, or audit trails (no ' +
139+
'`approval`, `approval_record`, `approval_step`, `workflow`, `process` tables). ' +
140+
'Approval/automation logic belongs in a separate FLOW authored after this ' +
141+
'blueprint, and the trail comes from platform history — never a hand-built table. ' +
142+
'Model only the PEOPLE the process references (approver / reviewer / owner) as ' +
143+
'`lookup` fields to the user object.\n' +
144+
'- If the goal implies an approval or automation process, add an `assumption` ' +
145+
'stating the approval *flow* will be drafted as a separate step (it is not part ' +
146+
'of this data blueprint).\n' +
134147
'- State the design choices you made as `assumptions`.\n' +
135148
'- If (and only if) a genuinely structure-deciding choice is unclear, put at most 1-2 ' +
136149
'short `questions`; otherwise pick the most likely interpretation and proceed.\n' +

0 commit comments

Comments
 (0)