ObjectStackObjectStack

Solution Blueprint

Solution Blueprint protocol schemas

Source: packages/spec/src/ai/solution-blueprint.zod.ts

TypeScript Usage

import { BlueprintAppSchema, BlueprintConditionSchema, BlueprintDashboardSchema, BlueprintFieldSchema, BlueprintNavItemSchema, BlueprintObjectSchema, BlueprintSeedSchema, BlueprintSummaryOperationsSchema, BlueprintViewSchema, BlueprintWidgetConditionSchema, SolutionBlueprintSchema, SolutionBlueprintStrictSchema } from '@objectstack/spec/ai';
import type { BlueprintApp, BlueprintCondition, BlueprintDashboard, BlueprintField, BlueprintNavItem, BlueprintObject, BlueprintSeed, BlueprintSummaryOperations, BlueprintView, BlueprintWidgetCondition, SolutionBlueprint, SolutionBlueprintStrict } from '@objectstack/spec/ai';

// Validate data
const result = BlueprintAppSchema.parse(data);

BlueprintApp

Properties

PropertyTypeRequiredDescription
namestringApp machine name (snake_case)
labelstringoptionalApp display label
iconstringoptionalLucide icon for the App Launcher
nav{ type: Enum<'object' | 'dashboard'>; target: string; label?: string; icon?: string }[]optionalNavigation entries; omit to auto-surface every created object and dashboard

Nested Shape: BlueprintApp.nav[number]

PropertyTypeRequiredDescription
typeEnum<'object' | 'dashboard'>optional (default: "object")What this nav entry opens
targetstringObject or dashboard machine name to surface (snake_case)
labelstringoptionalNav entry label (defaults to the target label/name)
iconstringoptionalLucide icon name for the nav entry

BlueprintCondition

Properties

PropertyTypeRequiredDescription
fieldstringField on the target object to filter by (e.g. "stock_quantity", "status")
opEnum<'lt' | 'lte' | 'gt' | 'gte' | 'eq' | 'ne'>Comparison operator
valuenumber | string | booleanComparison value — for a select field use its option VALUE, never its label (e.g. "completed", not "已完成")

BlueprintDashboard

Properties

PropertyTypeRequiredDescription
namestringDashboard machine name (snake_case)
labelstringoptionalHuman-readable dashboard label
widgets{ id: string; title?: string; object?: string; chart?: Enum<'metric' | 'bar' | 'line' | 'pie' | 'table'>; … }[]optionalWidgets to place on the dashboard

Nested Shape: BlueprintDashboard.widgets[number]

PropertyTypeRequiredDescription
idstringWidget id (snake_case)
titlestringoptionalWidget title
objectstringoptionalSource object for the widget
chartEnum<'metric' | 'bar' | 'line' | 'pie' | 'table'>optionalWidget visualization
measurestringoptionalThe field this widget aggregates (e.g. "amount", "probability"), or "count" to count records. The aggregation is chosen automatically from the field type — a money field SUMs, a percentage/rate AVERAGEs — so name the FIELD, not "total_amount". A "total revenue" widget sets measure:"amount"; an "average win rate" widget sets measure:"win_rate"; a "number of deals" widget sets measure:"count". Omit to let the builder infer from the title.
groupBystringoptionalThe field to break the widget down by — the category or time axis (e.g. "stage", "created_at"). A "by status" chart MUST set this to the status field; the title and this field MUST name the SAME field. Omit for a single-number metric.
condition{ field: string; op: Enum<'lt' | 'lte' | 'gt' | 'gte' | 'eq' | 'ne'>; value: number | string | boolean }optionalRestrict WHICH records the widget counts/aggregates when its title implies a threshold or status (e.g. "stock below 10" → {field:"stock_quantity", op:"lt", value:10}; "open tickets" → {field:"status", op:"eq", value:"open"}). Without it the widget covers ALL records — so a "低于10的备件预警" / "overdue" card would wrongly count everything. Omit when the widget genuinely spans every record.

BlueprintField

Properties

PropertyTypeRequiredDescription
namestringField machine name (snake_case)
labelstringoptionalHuman-readable field label
typeEnum<'text' | 'textarea' | 'email' | 'url' | 'phone' | 'password' | 'secret' | 'markdown' | 'html' | 'richtext' | 'number' | 'currency' | 'percent' | 'date' | … +35 more>Field data type
requiredbooleanoptionalWhether the field is required
referencestringoptionalTarget object name for lookup / master_detail relationship fields
options{ label: string; value: string }[]optionalChoices for select / multiselect / radio fields
summaryOperations{ object: string; function: Enum<'count' | 'sum' | 'avg' | 'min' | 'max'>; field?: string; relationshipField?: string; … }optionalREQUIRED when type is "summary" (a roll-up of child records: 任务总数 / 报名人数 / 合计金额 / 已完成任务数). Names the child object, the aggregation, and — for a qualified count/sum — the condition. A "summary" field without it materializes runtime-dead.
expressionstringoptionalREQUIRED when type is "formula" — the CEL body the field computes, e.g. "record.quantity * record.unit_price", or "record.order_no + ' · ' + record.customer" for a composed title. A "formula" field without it materializes runtime-dead: the engine builds its formula plan only from fields that HAVE an expression, so the field reads null everywhere, forever. Same failure shape as a "summary" with no summaryOperations. Note nameField on the object recommends a formula for numbered entities (invoice/ticket) — that formula needs THIS key, or the record title is blank on every card, lookup chip and breadcrumb.

Allowed Values: BlueprintField.type

  • text
  • textarea
  • email
  • url
  • phone
  • password
  • secret
  • markdown
  • html
  • richtext
  • number
  • currency
  • percent
  • date
  • datetime
  • time
  • boolean
  • toggle
  • select
  • multiselect
  • radio
  • checkboxes
  • lookup
  • master_detail
  • tree
  • user
  • image
  • file
  • avatar
  • video
  • audio
  • formula
  • summary
  • autonumber
  • composite
  • repeater
  • record
  • location
  • address
  • code
  • json
  • color
  • rating
  • slider
  • signature
  • qrcode
  • progress
  • tags
  • vector

Nested Shape: BlueprintField.summaryOperations

PropertyTypeRequiredDescription
objectstringThe CHILD object whose records are aggregated (snake_case). It must carry a lookup / master_detail field pointing back at this parent, or the roll-up never computes.
functionEnum<'count' | 'sum' | 'avg' | 'min' | 'max'>Aggregation: "数量 / 个数 / 计数" → count; "合计 / 总额 / 累计" → sum; "平均" → avg
fieldstringoptionalNumeric field on the CHILD object to aggregate. Ignored for "count" (pass "id" or omit it).
relationshipFieldstringoptionalThe child FK field pointing back at this parent. Auto-detected from the child's lookup / master_detail; set it only when the child has more than one reference to this parent.
conditions{ field: string; op: Enum<'lt' | 'lte' | 'gt' | 'gte' | 'eq' | 'ne'>; value: number | string | boolean }[]optionalCONDITIONAL roll-up: aggregate only the child rows matching these comparisons (ANDed). REQUIRED whenever the field name carries a qualifier — "已完成任务数 / 已收货金额 / 待处理工单数", any 已X / 未X / <某状态>的 count-or-sum → e.g. [{ field: "status", op: "eq", value: "completed" }]. WITHOUT it the roll-up silently counts EVERY child and reports a plausible-looking WRONG number, which is worse than a visible 0.
filteranyoptionalThe same predicate as a canonical query filter map (e.g. { status: "completed" }, { status: { $in: ["received", "partial"] } }). Use it when hand-authoring a blueprint; the structured design path uses conditions instead. Wins over conditions when both are given.

BlueprintNavItem

Properties

PropertyTypeRequiredDescription
typeEnum<'object' | 'dashboard'>optional (default: "object")What this nav entry opens
targetstringObject or dashboard machine name to surface (snake_case)
labelstringoptionalNav entry label (defaults to the target label/name)
iconstringoptionalLucide icon name for the nav entry

BlueprintObject

Properties

PropertyTypeRequiredDescription
namestringObject machine name (snake_case)
labelstringoptionalHuman-readable singular label
descriptionstringoptionalWhat this object represents
fields{ name: string; label?: string; type: Enum<'text' | 'textarea' | 'email' | 'url' | 'phone' | 'password' | 'secret' | …>; required?: boolean; … }[]Fields to create on the object
sharingModelEnum<'private' | 'public_read' | 'public_read_write' | 'controlled_by_parent'>optionalOrg-Wide Default record visibility (OWD) for INTERNAL users — the deliberate sharing choice for this object (ADR-0090). Canonical four only: private (owner-only) | public_read (everyone reads, owner writes) | public_read_write (everyone reads+writes) | controlled_by_parent (derived from the master record — ONLY for an object whose fields include a master_detail reference). SET it when the user's description implies a visibility intent — personal/private data (HR, 绩效, salary, 个人隐私) → "private"; shared reference data everyone edits → "public_read_write". Omit to accept the platform's deterministic default (business object → public_read_write; master-detail child → controlled_by_parent) — omitting on privacy-sensitive data silently over-shares it.
nameFieldstringoptionalThe record title field — which field holds the human-readable name shown on cards, lookup chips, breadcrumbs and search (ADR-0079). Set it to the object's text label field (e.g. "product_name"). For a numbered entity (invoice/ticket), set it to a formula field that composes number + name (e.g. "{order_no} · {customer}"). Omitting it lets the platform auto-pick a text field, but declaring it is strongly preferred.

Nested Shape: BlueprintObject.fields[number]

PropertyTypeRequiredDescription
namestringField machine name (snake_case)
labelstringoptionalHuman-readable field label
typeEnum<'text' | 'textarea' | 'email' | 'url' | 'phone' | 'password' | 'secret' | …>Field data type
requiredbooleanoptionalWhether the field is required
referencestringoptionalTarget object name for lookup / master_detail relationship fields
options{ label: string; value: string }[]optionalChoices for select / multiselect / radio fields
summaryOperations{ object: string; function: Enum<'count' | 'sum' | 'avg' | 'min' | 'max'>; field?: string; relationshipField?: string; … }optionalREQUIRED when type is "summary" (a roll-up of child records: 任务总数 / 报名人数 / 合计金额 / 已完成任务数). Names the child object, the aggregation, and — for a qualified count/sum — the condition. A "summary" field without it materializes runtime-dead.
expressionstringoptionalREQUIRED when type is "formula" — the CEL body the field computes, e.g. "record.quantity * record.unit_price", or "record.order_no + ' · ' + record.customer" for a composed title. A "formula" field without it materializes runtime-dead: the engine builds its formula plan only from fields that HAVE an expression, so the field reads null everywhere, forever. Same failure shape as a "summary" with no summaryOperations. Note nameField on the object recommends a formula for numbered entities (invoice/ticket) — that formula needs THIS key, or the record title is blank on every card, lookup chip and breadcrumb.

BlueprintSeed

Properties

PropertyTypeRequiredDescription
objectstringTarget object name (snake_case)
recordsRecord<string, any>[]Rows to seed

BlueprintSummaryOperations

Properties

PropertyTypeRequiredDescription
objectstringThe CHILD object whose records are aggregated (snake_case). It must carry a lookup / master_detail field pointing back at this parent, or the roll-up never computes.
functionEnum<'count' | 'sum' | 'avg' | 'min' | 'max'>Aggregation: "数量 / 个数 / 计数" → count; "合计 / 总额 / 累计" → sum; "平均" → avg
fieldstringoptionalNumeric field on the CHILD object to aggregate. Ignored for "count" (pass "id" or omit it).
relationshipFieldstringoptionalThe child FK field pointing back at this parent. Auto-detected from the child's lookup / master_detail; set it only when the child has more than one reference to this parent.
conditions{ field: string; op: Enum<'lt' | 'lte' | 'gt' | 'gte' | 'eq' | 'ne'>; value: number | string | boolean }[]optionalCONDITIONAL roll-up: aggregate only the child rows matching these comparisons (ANDed). REQUIRED whenever the field name carries a qualifier — "已完成任务数 / 已收货金额 / 待处理工单数", any 已X / 未X / <某状态>的 count-or-sum → e.g. [{ field: "status", op: "eq", value: "completed" }]. WITHOUT it the roll-up silently counts EVERY child and reports a plausible-looking WRONG number, which is worse than a visible 0.
filteranyoptionalThe same predicate as a canonical query filter map (e.g. { status: "completed" }, { status: { $in: ["received", "partial"] } }). Use it when hand-authoring a blueprint; the structured design path uses conditions instead. Wins over conditions when both are given.

Nested Shape: BlueprintSummaryOperations.conditions[number]

PropertyTypeRequiredDescription
fieldstringField on the target object to filter by (e.g. "stock_quantity", "status")
opEnum<'lt' | 'lte' | 'gt' | 'gte' | 'eq' | 'ne'>Comparison operator
valuenumber | string | booleanComparison value — for a select field use its option VALUE, never its label (e.g. "completed", not "已完成")

BlueprintView

Properties

PropertyTypeRequiredDescription
objectstringObject this view displays (snake_case)
namestringView machine name (snake_case)
labelstringoptionalHuman-readable view label
typeEnum<'list' | 'form' | 'kanban' | 'calendar' | 'gallery' | 'gantt'>optional (default: "list")View kind. Pick the surface that fits the data: "gallery" for a visual card/cover browse when the user asks for a 画廊/相册/卡片墙/封面/海报/图集 (a gallery / card wall / cover / poster grid) or the object has an image/avatar/file field worth showing as a card cover; "gantt" for a 甘特图/时间线/排期 (timeline / schedule) when the object has BOTH a start and an end date field; "kanban" for a board grouped by a status/select field; "calendar" for a single-date schedule; "form" for a record editor; else "list".
columnsstring[]optionalField names shown as columns (in order). For a gallery, INCLUDE the image/avatar/file field (it becomes the card cover); for a gantt, INCLUDE the start date column before the end date column.
groupBystringoptionalREQUIRED for kanban views: the select/status field whose options become the board columns (e.g. "stage", "status"). Without it a kanban renders as a plain list. Optional for gantt (groups leaf tasks into summary rows).

BlueprintWidgetCondition

Properties

PropertyTypeRequiredDescription
fieldstringField on the target object to filter by (e.g. "stock_quantity", "status")
opEnum<'lt' | 'lte' | 'gt' | 'gte' | 'eq' | 'ne'>Comparison operator
valuenumber | string | booleanComparison value — for a select field use its option VALUE, never its label (e.g. "completed", not "已完成")

SolutionBlueprint

Properties

PropertyTypeRequiredDescription
summarystringoptionalOne-line description of the proposed solution
assumptionsstring[]optional (default: [])Design assumptions made from the underspecified goal
questionsstring[]optionalAt most 1-2 structure-deciding questions to confirm before building
objects{ name: string; label?: string; description?: string; fields: object[]; … }[]Objects (tables) to create
views{ object: string; name: string; label?: string; type: Enum<'list' | 'form' | 'kanban' | 'calendar' | 'gallery' | 'gantt'>; … }[]optionalViews to create
dashboards{ name: string; label?: string; widgets?: object[] }[]optionalDashboards to create
app{ name: string; label?: string; icon?: string; nav?: object[] }optionalThe navigation shell (app) that surfaces the created objects/dashboards to end users
seedData{ object: string; records: Record<string, any>[] }[]optionalSuggested seed data (reported, not auto-applied in Phase C)

Nested Shape: SolutionBlueprint.objects[number]

PropertyTypeRequiredDescription
namestringObject machine name (snake_case)
labelstringoptionalHuman-readable singular label
descriptionstringoptionalWhat this object represents
fields{ name: string; label?: string; type: Enum<'text' | 'textarea' | 'email' | 'url' | 'phone' | 'password' | 'secret' | …>; required?: boolean; … }[]Fields to create on the object
sharingModelEnum<'private' | 'public_read' | 'public_read_write' | 'controlled_by_parent'>optionalOrg-Wide Default record visibility (OWD) for INTERNAL users — the deliberate sharing choice for this object (ADR-0090). Canonical four only: private (owner-only) | public_read (everyone reads, owner writes) | public_read_write (everyone reads+writes) | controlled_by_parent (derived from the master record — ONLY for an object whose fields include a master_detail reference). SET it when the user's description implies a visibility intent — personal/private data (HR, 绩效, salary, 个人隐私) → "private"; shared reference data everyone edits → "public_read_write". Omit to accept the platform's deterministic default (business object → public_read_write; master-detail child → controlled_by_parent) — omitting on privacy-sensitive data silently over-shares it.
nameFieldstringoptionalThe record title field — which field holds the human-readable name shown on cards, lookup chips, breadcrumbs and search (ADR-0079). Set it to the object's text label field (e.g. "product_name"). For a numbered entity (invoice/ticket), set it to a formula field that composes number + name (e.g. "{order_no} · {customer}"). Omitting it lets the platform auto-pick a text field, but declaring it is strongly preferred.

Nested Shape: SolutionBlueprint.views[number]

PropertyTypeRequiredDescription
objectstringObject this view displays (snake_case)
namestringView machine name (snake_case)
labelstringoptionalHuman-readable view label
typeEnum<'list' | 'form' | 'kanban' | 'calendar' | 'gallery' | 'gantt'>optional (default: "list")View kind. Pick the surface that fits the data: "gallery" for a visual card/cover browse when the user asks for a 画廊/相册/卡片墙/封面/海报/图集 (a gallery / card wall / cover / poster grid) or the object has an image/avatar/file field worth showing as a card cover; "gantt" for a 甘特图/时间线/排期 (timeline / schedule) when the object has BOTH a start and an end date field; "kanban" for a board grouped by a status/select field; "calendar" for a single-date schedule; "form" for a record editor; else "list".
columnsstring[]optionalField names shown as columns (in order). For a gallery, INCLUDE the image/avatar/file field (it becomes the card cover); for a gantt, INCLUDE the start date column before the end date column.
groupBystringoptionalREQUIRED for kanban views: the select/status field whose options become the board columns (e.g. "stage", "status"). Without it a kanban renders as a plain list. Optional for gantt (groups leaf tasks into summary rows).

Nested Shape: SolutionBlueprint.dashboards[number]

PropertyTypeRequiredDescription
namestringDashboard machine name (snake_case)
labelstringoptionalHuman-readable dashboard label
widgets{ id: string; title?: string; object?: string; chart?: Enum<'metric' | 'bar' | 'line' | 'pie' | 'table'>; … }[]optionalWidgets to place on the dashboard

Nested Shape: SolutionBlueprint.app

PropertyTypeRequiredDescription
namestringApp machine name (snake_case)
labelstringoptionalApp display label
iconstringoptionalLucide icon for the App Launcher
nav{ type: Enum<'object' | 'dashboard'>; target: string; label?: string; icon?: string }[]optionalNavigation entries; omit to auto-surface every created object and dashboard

Nested Shape: SolutionBlueprint.seedData[number]

PropertyTypeRequiredDescription
objectstringTarget object name (snake_case)
recordsRecord<string, any>[]Rows to seed

SolutionBlueprintStrict

Properties

PropertyTypeRequiredDescription
summarystringOne-line description of the proposed solution
assumptionsstring[]Design assumptions made from the underspecified goal
questionsstring[] | nullAt most 1-2 structure-deciding questions to confirm before building, or null
objects{ name: string; label: string | null; description: string | null; fields: object[]; … }[]Objects (tables) to create
views{ object: string; name: string; label: string | null; type: Enum<'list' | 'form' | 'kanban' | 'calendar' | 'gallery' | 'gantt'> | null; … }[] | nullViews to create, or null
dashboards{ name: string; label: string | null; widgets: object[] | null }[] | nullDashboards to create, or null
app{ name: string; label: string | null; icon: string | null; nav: object[] | null } | nullThe navigation shell (app) that surfaces the created objects/dashboards, or null

Nested Shape: SolutionBlueprintStrict.objects[number]

PropertyTypeRequiredDescription
namestringObject machine name (snake_case)
labelstring | nullHuman-readable singular label, or null
descriptionstring | nullWhat this object represents, or null
fields{ name: string; label: string | null; type: Enum<'text' | 'textarea' | 'email' | 'url' | 'phone' | 'password' | 'secret' | …>; required: boolean | null; … }[]Fields to create on the object
sharingModelEnum<'private' | 'public_read' | 'public_read_write' | 'controlled_by_parent'> | nullOrg-Wide Default record visibility (OWD) for INTERNAL users (ADR-0090), or null to accept the platform default (business object → public_read_write; master-detail child → controlled_by_parent). SET it when the user's description implies a visibility intent: personal/private data (HR, 绩效, salary, 个人隐私) → "private" (owner-only); "public_read" = everyone reads, owner writes; "public_read_write" = everyone reads+writes; "controlled_by_parent" ONLY for an object with a master_detail reference field. Null on privacy-sensitive data silently over-shares it.
nameFieldstring | nullThe record title field — which field holds the human-readable name shown on cards, lookup chips, breadcrumbs and search (ADR-0079), or null to let the platform auto-pick a text field. Set it to the object's text label field (e.g. "product_name") — snake_case. For a numbered entity (invoice/ticket), set it to a formula field that composes number + name (e.g. "{order_no} · {customer}"). Declaring it is strongly preferred over null.

Nested Shape: SolutionBlueprintStrict.views[number]

PropertyTypeRequiredDescription
objectstringObject this view displays (snake_case)
namestringView machine name (snake_case)
labelstring | nullHuman-readable view label, or null
typeEnum<'list' | 'form' | 'kanban' | 'calendar' | 'gallery' | 'gantt'> | nullView kind, or null for list. "gallery" = visual card/cover browse (画廊/相册/卡片墙/封面/海报, or an object with an image/avatar/file field); "gantt" = timeline/schedule (甘特图/时间线/排期, object with BOTH a start and an end date field); "kanban" = board grouped by a status/select field; "calendar" = single-date schedule; "form" = record editor.
columnsstring[] | nullField names shown as columns, or null. For a gallery, INCLUDE the image/avatar/file field (becomes the card cover); for a gantt, INCLUDE the start date column before the end date column.
groupBystring | nullREQUIRED for kanban: the select/status field whose options become the board columns (e.g. "stage"). Optional for gantt (groups leaf tasks). Null for list/form/calendar/gallery.

Nested Shape: SolutionBlueprintStrict.dashboards[number]

PropertyTypeRequiredDescription
namestringDashboard machine name (snake_case)
labelstring | nullHuman-readable dashboard label, or null
widgets{ id: string; title: string | null; object: string | null; chart: Enum<'metric' | 'bar' | 'line' | 'pie' | 'table'> | null; … }[] | nullWidgets to place on the dashboard, or null

Nested Shape: SolutionBlueprintStrict.app

PropertyTypeRequiredDescription
namestringApp machine name (snake_case)
labelstring | nullApp display label, or null
iconstring | nullLucide icon for the App Launcher, or null
nav{ type: Enum<'object' | 'dashboard'>; target: string; label: string | null; icon: string | null }[] | nullNavigation entries; null to auto-surface every created object and dashboard

On this page