Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .changeset/adr-0033-blueprint-openai-strict.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
"@objectstack/spec": patch
"@objectstack/service-ai": patch
---

fix(ai): make ADR-0033 blueprint authoring work with OpenAI structured outputs

Two bugs surfaced by a live end-to-end run (Studio chat → blueprint → draft → review → publish) against a real model (OpenAI via the Vercel AI Gateway) — both invisible to the existing unit tests:

1. **`propose_blueprint` failed against OpenAI strict structured outputs.** `SolutionBlueprintSchema` uses optional fields and a free-form `seedData` record; OpenAI's strict mode requires every property listed in `required` and rejects open `additionalProperties`, so `generateObject` errored (`'required' … must include every key in properties`) and the agent silently fell back to free-text. Adds `SolutionBlueprintStrictSchema` — a strict-compatible mirror (optional → nullable, no `z.record`) used **only** as the `generateObject` output contract. The lenient `SolutionBlueprintSchema` (and every existing consumer/test) is unchanged; the blueprint tools strip the `null`s the strict contract emits so downstream stays clean.

2. **Tool-only assistant turns failed to persist.** `ai_messages.content` is required, but an assistant turn that only calls a tool has no text, so the insert failed, the turn was dropped, and the next turn lost context (the agent re-proposed instead of applying the confirmed blueprint). `ObjectQLConversationService.addMessage` now synthesizes a readable placeholder from the tool names (`(called propose_blueprint)`) plus a defensive non-empty fallback.

With both fixes the full plan-first loop runs end-to-end on OpenAI models: propose → confirm → batch-draft objects/views/dashboards/app → review/diff → publish.
1 change: 1 addition & 0 deletions content/docs/references/ai/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ This section contains all protocol schemas for the ai layer of ObjectStack.
<Card href="/docs/references/ai/mcp" title="Mcp" description="Source: packages/spec/src/ai/mcp.zod.ts" />
<Card href="/docs/references/ai/model-registry" title="Model Registry" description="Source: packages/spec/src/ai/model-registry.zod.ts" />
<Card href="/docs/references/ai/skill" title="Skill" description="Source: packages/spec/src/ai/skill.zod.ts" />
<Card href="/docs/references/ai/solution-blueprint" title="Solution Blueprint" description="Source: packages/spec/src/ai/solution-blueprint.zod.ts" />
<Card href="/docs/references/ai/tool" title="Tool" description="Source: packages/spec/src/ai/tool.zod.ts" />
<Card href="/docs/references/ai/usage" title="Usage" description="Source: packages/spec/src/ai/usage.zod.ts" />
</Cards>
1 change: 1 addition & 0 deletions content/docs/references/ai/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"mcp",
"model-registry",
"skill",
"solution-blueprint",
"tool",
"usage"
]
Expand Down
161 changes: 161 additions & 0 deletions content/docs/references/ai/solution-blueprint.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
---
title: Solution Blueprint
description: Solution Blueprint protocol schemas
---

{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs go in content/docs/guides/. */}

Solution Blueprint Schema (ADR-0033 §4 — plan-first authoring)

The structured-output target an AI agent emits for a *high-level* goal

("build me a project-management system") instead of transcribing a field

list. It is a **simplified proposal shape** — deliberately lighter than the

full [ObjectSchema](ObjectSchema) / [ViewSchema](ViewSchema) / [DashboardSchema](DashboardSchema).

The `apply_blueprint` tool expands each entry into a proper metadata body

and stages it as a draft (so the per-type Zod schema still validates the

real artifact at write time).

The blueprint is **never persisted on its own**: the agent presents it for

conversational confirmation/edit (cheap), and only on human approval does it

batch-draft. This is the safety valve for low-specificity input.

<Callout type="info">
**Source:** `packages/spec/src/ai/solution-blueprint.zod.ts`
</Callout>

## TypeScript Usage

```typescript
import { BlueprintApp, BlueprintDashboard, BlueprintField, BlueprintNavItem, BlueprintObject, BlueprintSeed, BlueprintView, SolutionBlueprint } from '@objectstack/spec/ai';
import type { BlueprintApp, BlueprintDashboard, BlueprintField, BlueprintNavItem, BlueprintObject, BlueprintSeed, BlueprintView, SolutionBlueprint } from '@objectstack/spec/ai';

// Validate data
const result = BlueprintApp.parse(data);
```

---

## BlueprintApp

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **name** | `string` | ✅ | App machine name (snake_case) |
| **label** | `string` | optional | App display label |
| **icon** | `string` | optional | Lucide icon for the App Launcher |
| **nav** | `Object[]` | optional | Navigation entries; omit to auto-surface every created object and dashboard |


---

## BlueprintDashboard

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **name** | `string` | ✅ | Dashboard machine name (snake_case) |
| **label** | `string` | optional | Human-readable dashboard label |
| **widgets** | `Object[]` | optional | Widgets to place on the dashboard |


---

## BlueprintField

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **name** | `string` | ✅ | Field machine name (snake_case) |
| **label** | `string` | optional | Human-readable field label |
| **type** | `Enum<'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' \| 'image' \| 'file' \| 'avatar' \| 'video' \| 'audio' \| 'formula' \| 'summary' \| 'autonumber' \| 'composite' \| 'repeater' \| 'record' \| 'location' \| 'address' \| 'code' \| 'json' \| 'color' \| 'rating' \| 'slider' \| 'signature' \| 'qrcode' \| 'progress' \| 'tags' \| 'vector'>` | ✅ | Field data type |
| **required** | `boolean` | optional | Whether the field is required |
| **reference** | `string` | optional | Target object name for lookup / master_detail relationship fields |
| **options** | `Object[]` | optional | Choices for select / multiselect / radio fields |


---

## BlueprintNavItem

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **type** | `Enum<'object' \| 'dashboard'>` | ✅ | What this nav entry opens |
| **target** | `string` | ✅ | Object or dashboard machine name to surface (snake_case) |
| **label** | `string` | optional | Nav entry label (defaults to the target label/name) |
| **icon** | `string` | optional | Lucide icon name for the nav entry |


---

## BlueprintObject

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **name** | `string` | ✅ | Object machine name (snake_case) |
| **label** | `string` | optional | Human-readable singular label |
| **description** | `string` | optional | What this object represents |
| **fields** | `Object[]` | ✅ | Fields to create on the object |


---

## BlueprintSeed

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **object** | `string` | ✅ | Target object name (snake_case) |
| **records** | `Record<string, any>[]` | ✅ | Rows to seed |


---

## BlueprintView

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **object** | `string` | ✅ | Object this view displays (snake_case) |
| **name** | `string` | ✅ | View machine name (snake_case) |
| **label** | `string` | optional | Human-readable view label |
| **type** | `Enum<'list' \| 'form' \| 'kanban' \| 'calendar'>` | ✅ | View kind |
| **columns** | `string[]` | optional | Field names shown as columns (in order) |


---

## SolutionBlueprint

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **summary** | `string` | ✅ | One-line description of the proposed solution |
| **assumptions** | `string[]` | ✅ | Design assumptions made from the underspecified goal |
| **questions** | `string[]` | optional | At most 1-2 structure-deciding questions to confirm before building |
| **objects** | `Object[]` | ✅ | Objects (tables) to create |
| **views** | `Object[]` | optional | Views to create |
| **dashboards** | `Object[]` | optional | Dashboards to create |
| **app** | `Object` | optional | The navigation shell (app) that surfaces the created objects/dashboards to end users |
| **seedData** | `Object[]` | optional | Suggested seed data (reported, not auto-applied in Phase C) |


---

1 change: 1 addition & 0 deletions content/docs/references/api/discovery.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ const result = ApiRoutes.parse(data);
| **graphql** | `string` | optional | e.g. /graphql |
| **packages** | `string` | optional | e.g. /api/v1/packages |
| **workflow** | `string` | optional | e.g. /api/v1/workflow |
| **approvals** | `string` | optional | e.g. /api/v1/approvals |
| **realtime** | `string` | optional | e.g. /api/v1/realtime |
| **notifications** | `string` | optional | e.g. /api/v1/notifications |
| **ai** | `string` | optional | e.g. /api/v1/ai |
Expand Down
4 changes: 2 additions & 2 deletions content/docs/references/api/metadata.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ Metadata query with filtering, sorting, and pagination

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **types** | `Enum<'object' \| 'field' \| 'trigger' \| 'validation' \| 'hook' \| 'view' \| 'page' \| 'dashboard' \| 'app' \| 'action' \| 'report' \| 'flow' \| 'workflow' \| 'approval' \| 'job' \| 'datasource' \| 'external_catalog' \| 'translation' \| 'router' \| 'function' \| 'service' \| 'email_template' \| 'permission' \| 'profile' \| 'role' \| 'agent' \| 'tool' \| 'skill'>[]` | optional | Filter by metadata types |
| **types** | `Enum<'object' \| 'field' \| 'trigger' \| 'validation' \| 'hook' \| 'view' \| 'page' \| 'dashboard' \| 'app' \| 'action' \| 'report' \| 'flow' \| 'job' \| 'datasource' \| 'external_catalog' \| 'translation' \| 'router' \| 'function' \| 'service' \| 'email_template' \| 'permission' \| 'profile' \| 'role' \| 'agent' \| 'tool' \| 'skill'>[]` | optional | Filter by metadata types |
| **namespaces** | `string[]` | optional | Filter by namespaces |
| **packageId** | `string` | optional | Filter by owning package |
| **search** | `string` | optional | Full-text search query |
Expand Down Expand Up @@ -349,7 +349,7 @@ Metadata query with filtering, sorting, and pagination

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **type** | `Enum<'object' \| 'field' \| 'trigger' \| 'validation' \| 'hook' \| 'view' \| 'page' \| 'dashboard' \| 'app' \| 'action' \| 'report' \| 'flow' \| 'workflow' \| 'approval' \| 'job' \| 'datasource' \| 'external_catalog' \| 'translation' \| 'router' \| 'function' \| 'service' \| 'email_template' \| 'permission' \| 'profile' \| 'role' \| 'agent' \| 'tool' \| 'skill'>` | ✅ | Metadata type |
| **type** | `Enum<'object' \| 'field' \| 'trigger' \| 'validation' \| 'hook' \| 'view' \| 'page' \| 'dashboard' \| 'app' \| 'action' \| 'report' \| 'flow' \| 'job' \| 'datasource' \| 'external_catalog' \| 'translation' \| 'router' \| 'function' \| 'service' \| 'email_template' \| 'permission' \| 'profile' \| 'role' \| 'agent' \| 'tool' \| 'skill'>` | ✅ | Metadata type |
| **name** | `string` | ✅ | Item name (snake_case) |
| **data** | `Record<string, any>` | ✅ | Metadata payload |
| **namespace** | `string` | optional | Optional namespace |
Expand Down
102 changes: 2 additions & 100 deletions content/docs/references/api/package-api.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ DELETE /api/v1/packages/:packageId — Uninstall a package
## TypeScript Usage

```typescript
import { GetInstalledPackageRequest, GetInstalledPackageResponse, ListInstalledPackagesRequest, ListInstalledPackagesResponse, PackageApiErrorCode, PackageInstallRequest, PackageInstallResponse, PackagePathParams, PackageRollbackRequest, PackageRollbackResponse, PackageUpgradeRequest, PackageUpgradeResponse, ResolveDependenciesRequest, ResolveDependenciesResponse, UninstallPackageApiRequest, UninstallPackageApiResponse, UploadArtifactRequest, UploadArtifactResponse } from '@objectstack/spec/api';
import type { GetInstalledPackageRequest, GetInstalledPackageResponse, ListInstalledPackagesRequest, ListInstalledPackagesResponse, PackageApiErrorCode, PackageInstallRequest, PackageInstallResponse, PackagePathParams, PackageRollbackRequest, PackageRollbackResponse, PackageUpgradeRequest, PackageUpgradeResponse, ResolveDependenciesRequest, ResolveDependenciesResponse, UninstallPackageApiRequest, UninstallPackageApiResponse, UploadArtifactRequest, UploadArtifactResponse } from '@objectstack/spec/api';
import { GetInstalledPackageRequest, ListInstalledPackagesRequest, PackageApiErrorCode, PackagePathParams, PackageRollbackRequest, PackageRollbackResponse, PackageUpgradeResponse, ResolveDependenciesResponse, UninstallPackageApiRequest, UninstallPackageApiResponse, UploadArtifactRequest, UploadArtifactResponse } from '@objectstack/spec/api';
import type { GetInstalledPackageRequest, ListInstalledPackagesRequest, PackageApiErrorCode, PackagePathParams, PackageRollbackRequest, PackageRollbackResponse, PackageUpgradeResponse, ResolveDependenciesResponse, UninstallPackageApiRequest, UninstallPackageApiResponse, UploadArtifactRequest, UploadArtifactResponse } from '@objectstack/spec/api';

// Validate data
const result = GetInstalledPackageRequest.parse(data);
Expand All @@ -54,22 +54,6 @@ const result = GetInstalledPackageRequest.parse(data);
| **packageId** | `string` | ✅ | Package identifier |


---

## GetInstalledPackageResponse

Get installed package response

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **success** | `boolean` | ✅ | Operation success status |
| **error** | `Object` | optional | Error details if success is false |
| **meta** | `Object` | optional | Response metadata |
| **data** | `Object` | ✅ | Installed package details |


---

## ListInstalledPackagesRequest
Expand All @@ -86,22 +70,6 @@ List installed packages request
| **cursor** | `string` | optional | Cursor for pagination |


---

## ListInstalledPackagesResponse

List installed packages response

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **success** | `boolean` | ✅ | Operation success status |
| **error** | `Object` | optional | Error details if success is false |
| **meta** | `Object` | optional | Response metadata |
| **data** | `Object` | ✅ | |


---

## PackageApiErrorCode
Expand All @@ -123,39 +91,6 @@ List installed packages response
* `upload_failed`


---

## PackageInstallRequest

Install package request

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **manifest** | `Object` | ✅ | Package manifest to install |
| **settings** | `Record<string, any>` | optional | User-provided settings at install time |
| **enableOnInstall** | `boolean` | ✅ | Whether to enable immediately after install |
| **platformVersion** | `string` | optional | Current platform version for compatibility verification |
| **artifactRef** | `Object` | optional | Artifact reference for marketplace installation |


---

## PackageInstallResponse

Install package response

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **success** | `boolean` | ✅ | Operation success status |
| **error** | `Object` | optional | Error details if success is false |
| **meta** | `Object` | optional | Response metadata |
| **data** | `Object` | ✅ | |


---

## PackagePathParams
Expand Down Expand Up @@ -198,25 +133,6 @@ Rollback package response
| **data** | `Object` | ✅ | |


---

## PackageUpgradeRequest

Upgrade package request

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **packageId** | `string` | ✅ | Package ID to upgrade |
| **targetVersion** | `string` | optional | Target version (defaults to latest) |
| **manifest** | `Object` | optional | New manifest for the target version |
| **createSnapshot** | `boolean` | ✅ | Whether to create a pre-upgrade backup snapshot |
| **mergeStrategy** | `Enum<'keep-custom' \| 'accept-incoming' \| 'three-way-merge'>` | ✅ | How to handle customer customizations |
| **dryRun** | `boolean` | ✅ | Preview upgrade without making changes |
| **skipValidation** | `boolean` | ✅ | Skip pre-upgrade compatibility checks |


---

## PackageUpgradeResponse
Expand All @@ -233,20 +149,6 @@ Upgrade package response
| **data** | `Object` | ✅ | |


---

## ResolveDependenciesRequest

Resolve dependencies request

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **manifest** | `Object` | ✅ | Package manifest to resolve dependencies for |
| **platformVersion** | `string` | optional | Current platform version for compatibility filtering |


---

## ResolveDependenciesResponse
Expand Down
Loading