howone 0.1.22 → 0.1.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/package.json +1 -1
  2. package/templates/nextjs/lib/sdk.ts +3 -0
  3. package/templates/vite/.howone/skills/howone-sdk/01-architect/01-app-generation.md +183 -69
  4. package/templates/vite/.howone/skills/howone-sdk/01-architect/02-manifest-codegen.md +98 -23
  5. package/templates/vite/.howone/skills/howone-sdk/02-database/01-schema-design.md +463 -69
  6. package/templates/vite/.howone/skills/howone-sdk/02-database/02-schema-operations.md +366 -64
  7. package/templates/vite/.howone/skills/howone-sdk/02-database/03-data-access-patterns.md +204 -67
  8. package/templates/vite/.howone/skills/howone-sdk/02-database/04-query-dsl-and-responses.md +237 -0
  9. package/templates/vite/.howone/skills/howone-sdk/02-database/05-ai-persistence-patterns.md +372 -0
  10. package/templates/vite/.howone/skills/howone-sdk/03-sdk/01-client-setup.md +58 -36
  11. package/templates/vite/.howone/skills/howone-sdk/03-sdk/02-entity-operations.md +67 -0
  12. package/templates/vite/.howone/skills/howone-sdk/03-sdk/03-auth.md +267 -469
  13. package/templates/vite/.howone/skills/howone-sdk/03-sdk/04-react-integration.md +113 -322
  14. package/templates/vite/.howone/skills/howone-sdk/03-sdk/07-ai-action-calls.md +95 -48
  15. package/templates/vite/.howone/skills/howone-sdk/03-sdk/08-extension-boundaries.md +226 -0
  16. package/templates/vite/.howone/skills/howone-sdk/04-ai/01-ai-capability-architecture.md +205 -0
  17. package/templates/vite/.howone/skills/howone-sdk/04-ai/02-workflow-contract-rules.md +426 -0
  18. package/templates/vite/.howone/skills/howone-sdk/04-ai/03-ai-sdk-handoff.md +219 -0
  19. package/templates/vite/.howone/skills/howone-sdk/04-ai/04-service-capability-catalog.md +281 -0
  20. package/templates/vite/.howone/skills/howone-sdk/04-ai/05-workflow-operations.md +256 -0
  21. package/templates/vite/.howone/skills/howone-sdk/04-ai/06-ai-feature-playbooks.md +296 -0
  22. package/templates/vite/.howone/skills/howone-sdk/SKILL.md +83 -15
  23. package/templates/vite/.howone/skills/howone-sdk/agents/openai.yaml +2 -2
  24. package/templates/vite/package.json +1 -1
  25. package/templates/vite/src/lib/sdk.ts +3 -0
  26. package/templates/vite/.howone/skills/howone-sdk/04-ai/.gitkeep +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "howone",
3
- "version": "0.1.22",
3
+ "version": "0.1.25",
4
4
  "private": false,
5
5
  "description": "HowOne command line tools for creating app templates.",
6
6
  "type": "module",
@@ -18,6 +18,9 @@ export const entities = defineEntities({
18
18
 
19
19
  export const ai = defineAiActions({
20
20
  // Add generated AI action bindings here, for example:
21
+ // import { z } from "zod" and define Zod schemas before using defineAiAction.
22
+ // Do not paste JSON Schema objects from .howone/ai/manifest.json here directly.
23
+ // With outputSchema configured, howone.ai.<action>.run() returns the validated finalResult payload.
21
24
  // generateImage: defineAiAction("generateImage", {
22
25
  // inputSchema: generateImageInputSchema,
23
26
  // outputSchema: generateImageOutputSchema,
@@ -1,101 +1,215 @@
1
1
  # App Generation Architect
2
2
 
3
- Use this file to decide the HowOne app implementation path before reading low-level SDK or
4
- database references.
3
+ Use this file before building or changing a HowOne generated app. It decides which platform tracks
4
+ must be used and what order to execute them in.
5
5
 
6
- ## Responsibility Split
6
+ This is the planning layer. It should prevent the agent from jumping straight into UI code while
7
+ missing schema, auth, AI, manifest, or SDK binding contracts.
7
8
 
8
- HowOne generated apps have three layers:
9
+ ## Platform Shape
9
10
 
10
- 1. **Backend contract**: dynamic entities, access rules, indexes, schema versions, and synced
11
- manifests.
12
- 2. **SDK binding**: `src/lib/sdk.ts` converts manifests into typed entity and AI clients.
13
- 3. **Frontend experience**: React/UI code calls the SDK and owns app-specific interaction design.
11
+ HowOne generated apps have four product layers:
14
12
 
15
- Do not skip the binding layer. The UI should call `src/lib/sdk.ts`, not raw entity names guessed
16
- from the user prompt.
13
+ | Layer | Source of truth | App code should do |
14
+ |---|---|---|
15
+ | Backend database | synced `.howone/database/manifest.json` | generate entity types/bindings and call SDK |
16
+ | AI capabilities | synced `.howone/ai/manifest.json` + workflow status | generate AI action bindings and call SDK |
17
+ | SDK runtime | `@howone/sdk` + `src/lib/sdk.ts` | centralize env/auth/entities/AI/upload |
18
+ | Frontend app | user experience | own UI, state, feedback, forms, navigation |
17
19
 
18
- Core source-of-truth rule from the HowOne docs:
20
+ The agent may propose schema/capability changes, but the validated/synced manifests drive code.
19
21
 
20
22
  ```text
21
- agent proposal != source of truth
22
- validated backend manifest = source of truth
23
- generated files = compiler/binding output
23
+ user request -> architecture decision -> backend/AI contracts -> sync manifests -> sdk binding -> UI
24
24
  ```
25
25
 
26
- The agent proposes structured changes and writes app code. The runtime/schema tools validate,
27
- apply, version, and sync manifest facts.
26
+ Do not skip the binding layer. UI code should import `howone` from `src/lib/sdk.ts`, not construct
27
+ raw URLs or guessed entity/action names.
28
28
 
29
- ## Choose Data Posture First
29
+ ## First Decision: What Surfaces Are Touched?
30
30
 
31
- Before designing schema or UI calls, classify the data:
31
+ Classify the request before editing.
32
32
 
33
- | Posture | Backend Access | Frontend Read |
33
+ | User request says | Touched surfaces | Required references |
34
34
  |---|---|---|
35
- | Private user data | `authenticated: own`, `public: none` | `howone.entities.Entity.query.mine(...)` |
36
- | Authenticated shared app data | `authenticated: all`, `public: none` | `howone.entities.Entity.query(...)` |
37
- | Public list data | `authenticated: all`, `public: list` | `howone.public.entities.Entity.query(...)` |
38
- | Public scoped share data | usually `authenticated: own`, `public: scoped` | `howone.public.entities.Entity.queryScoped(...)` |
35
+ | "store/save/history/list/my data" | database + SDK + UI | `02-database/`, `03-sdk/02-entity-operations.md` |
36
+ | "login/account/my/private" | auth + database access | `03-sdk/03-auth.md`, `03-sdk/04-react-integration.md` |
37
+ | "public page/share/link/landing/catalog" | public access + SDK public namespace | `02-database/03-data-access-patterns.md` |
38
+ | "AI/generate/analyze/summarize/research/edit image/video/audio" | AI contract + workflow + SDK | `04-ai/` |
39
+ | "upload file/image/audio/pdf" | upload + maybe AI URL input | `03-sdk/05-file-upload.md`, `04-ai/02-workflow-contract-rules.md` |
40
+ | "change schema/add field/new table" | schema operations + manifest codegen | `02-database/02-schema-operations.md`, `01-architect/02-manifest-codegen.md` |
41
+ | "frontend only" | SDK usage + UI | `03-sdk/01-client-setup.md` and relevant SDK docs |
39
42
 
40
- If the user says "my todos", "my notes", "per user", "login required", or "data isolation",
41
- default to private user data.
43
+ If multiple surfaces are touched, read one reference from each surface before editing.
42
44
 
43
- ## Backend Feature Workflow
45
+ ## Data Posture Decision
44
46
 
45
- When a request adds persistence or changes data shape:
47
+ Choose data posture before schema and UI.
46
48
 
47
- 1. Read `02-database/01-schema-design.md` and `02-database/02-schema-operations.md`.
48
- 2. Inspect the current schema/manifest.
49
- 3. Design one complete schema patch for the feature.
50
- 4. Preview the schema patch.
51
- 5. Apply the exact previewed operations if risk is acceptable.
52
- 6. Sync schema artifacts using the returned `next.versionId`.
53
- 7. Read `.howone/database/manifest.json`.
54
- 8. Read `01-architect/02-manifest-codegen.md`.
55
- 9. Update `src/lib/sdk.ts`.
56
- 10. Read the needed SDK file, usually `03-sdk/02-entity-operations.md` and sometimes `03-sdk/03-auth.md`.
57
- 11. Update the UI.
58
- 12. Validate.
49
+ | Product need | Access contract | SDK read |
50
+ |---|---|---|
51
+ | per-user private data | authenticated own, public none | `howone.entities.X.query.mine()` |
52
+ | logged-in shared admin/team data | authenticated all, public none | `howone.entities.X.query()` |
53
+ | anonymous public catalog/feed | authenticated all, public list | `howone.public.entities.X.query()` |
54
+ | one public share/detail page | authenticated own/all, public scoped | `howone.public.entities.X.queryScoped()` |
55
+ | anonymous form submission | authenticated all, public create scoped/any | `howone.public.entities.X.create()` |
56
+ | AI generation history | authenticated own, public none | `runAiActionAndPersist()` + `query.mine()` |
57
+ | AI public share | private history + public scoped share entity | two entities |
59
58
 
60
- Do not preview a patch and then execute different single operations. Related schema changes for
61
- one feature should be grouped into one preview/apply cycle.
59
+ Defaults:
60
+
61
+ - "my" / "per user" / "private" -> authenticated own.
62
+ - "landing page" / "blog" / "gallery" -> public list only if fields are safe.
63
+ - "share link" / "QR" / "public result" -> public scoped, small `maxLimit`.
64
+ - "AI history" -> private history entity; do not make it public just for sharing.
62
65
 
63
66
  ## Auth Decision
64
67
 
65
- Use hosted auth when the app just needs login:
68
+ | Need | SDK config | Provider behavior |
69
+ |---|---|---|
70
+ | default HowOne login | `createClient({ projectId, env })` | hosted login |
71
+ | custom designed login page using HowOne auth APIs | `auth: 'custom'`, `HowOneProvider auth="none"` | app owns login UI |
72
+ | external identity provider/JWT | `auth: { mode: 'headless', adapter }` | adapter owns token/user |
73
+ | public-only app | `auth: 'none'` | no auth guard |
74
+
75
+ Rules:
66
76
 
67
- ```tsx
68
- <HowOneProvider auth="required">
69
- <App />
70
- </HowOneProvider>
71
- ```
77
+ - Keep the bottom-right HowOne `FloatingButton` by default unless explicitly hidden.
78
+ - SDK must not add toast/overlay/login-page UI.
79
+ - Use `client.me()` or `client.requireMe()` for first-load user resolution.
80
+ - Do not use `auth.isAuthenticated()` as the only initial truth when user data must be loaded.
81
+
82
+ ## Backend Feature Workflow
83
+
84
+ Use when persistence or schema changes are needed:
85
+
86
+ 1. Read `02-database/01-schema-design.md`.
87
+ 2. Read `02-database/02-schema-operations.md`.
88
+ 3. Inspect current schema state/manifest.
89
+ 4. Design complete entity contract: fields, required, access, indexes, presentation.
90
+ 5. Preview one complete schema patch.
91
+ 6. Apply the exact previewed patch if risk is acceptable.
92
+ 7. Sync schema artifacts from returned version.
93
+ 8. Read `.howone/database/manifest.json`.
94
+ 9. Update `src/lib/sdk.ts` from manifest using `01-architect/02-manifest-codegen.md`.
95
+ 10. Implement UI with `howone.entities.*` or `howone.public.entities.*`.
96
+ 11. Validate build/tests.
97
+
98
+ Risk stops:
99
+
100
+ - deleting entity/field;
101
+ - making required field without default;
102
+ - broadening public access;
103
+ - enabling public write;
104
+ - changing owner/public scope semantics.
105
+
106
+ ## AI Feature Workflow
107
+
108
+ Use when AI capability/workflow is needed:
109
+
110
+ 1. Read `04-ai/01-ai-capability-architecture.md`.
111
+ 2. Read `04-ai/04-service-capability-catalog.md` to verify support.
112
+ 3. Pick a playbook from `04-ai/06-ai-feature-playbooks.md` when applicable.
113
+ 4. Design schemas with `04-ai/02-workflow-contract-rules.md`.
114
+ 5. Preview/apply AI capability patch.
115
+ 6. Sync `.howone/ai/manifest.json`.
116
+ 7. Submit external workflow create/update using `04-ai/05-workflow-operations.md`.
117
+ 8. Preserve returned `request_id`.
118
+ 9. Poll status until terminal; preserve `workflowConfigID` on success.
119
+ 10. Update `src/lib/sdk.ts` with `04-ai/03-ai-sdk-handoff.md`.
120
+ 11. Implement UI through `howone.ai.*`.
121
+ 12. If output persists, use `02-database/05-ai-persistence-patterns.md`.
122
+
123
+ Do not build fake AI. If the required capability is unsupported, report the exact gap.
124
+
125
+ ## Manifest Codegen Workflow
126
+
127
+ Run after database or AI sync:
128
+
129
+ 1. Read current `src/lib/sdk.ts`.
130
+ 2. Read synced manifests.
131
+ 3. Preserve existing exports and naming style.
132
+ 4. Generate/update:
133
+ - entity `Record/Create/Update` types;
134
+ - optional exported `*EntityDefinition` for guards;
135
+ - `client.entity<...>()` bindings;
136
+ - AI Zod schemas and `defineAiAction(...)`;
137
+ - composed `howone` export.
138
+ 5. Never write generated source under `.howone/`.
72
139
 
73
- Use custom in-app auth when the user asks for a designed login page or multiple login methods:
140
+ ## Common User Situations
74
141
 
75
- ```tsx
76
- <HowOneProvider auth="none" brand="hidden">
77
- <App />
78
- </HowOneProvider>
142
+ ### User asks for "just a frontend"
143
+
144
+ Still check whether UI needs stored data, auth, upload, or AI. If it only renders static/local state,
145
+ do not invent schema or workflow. If it saves anything, use database flow.
146
+
147
+ ### User asks for "AI app" but no persistence
148
+
149
+ Design AI capability and SDK binding only. Keep results in app state. Do not create entities unless
150
+ history, refresh resilience, user library, or share page is needed.
151
+
152
+ ### User asks for "AI app with history"
153
+
154
+ Design AI first, then database:
155
+
156
+ ```text
157
+ AI output contract -> Generation entity -> runAiActionAndPersist -> history query.mine()
79
158
  ```
80
159
 
81
- Custom login must call `howone.auth.setToken(token)` after Email OTP, Phone OTP, Google, or
82
- GitHub succeeds, then verify the session with `await howone.me({ refresh: true })`.
160
+ Do not put history fields into workflow output unless the AI itself must generate them.
161
+
162
+ ### User asks for "public AI result"
163
+
164
+ Use two entities:
165
+
166
+ - private `Generation` for owner history and retry;
167
+ - public scoped `SharedGeneration` for anonymous viewing.
168
+
169
+ Do not expose private prompt/history broadly.
170
+
171
+ ### User asks for "latest/current/research"
172
+
173
+ Use AI workflow with web search/crawling capability. If app also lists saved briefings, add entity
174
+ persistence after output contract.
175
+
176
+ ### User asks to modify existing AI behavior
177
+
178
+ If only behavior changes, use external workflow update with `workflowConfigID`.
179
+ If input/output changes, update AI capability contract first, sync manifest, then update workflow
180
+ and SDK bindings.
181
+
182
+ ### User asks to change schema used by UI
183
+
184
+ Change backend schema first, sync, regenerate SDK, then update UI. Do not patch UI types from memory.
185
+
186
+ ### User asks for custom auth
187
+
188
+ Use `auth: 'custom'` or headless `AuthAdapter`. App owns visible login UI. Keep SDK callbacks/data
189
+ only.
190
+
191
+ ## Implementation Guardrails
83
192
 
84
- ## Generated Binding Rules
193
+ - Use `@howone/sdk` typed clients before `raw`.
194
+ - Use `howone.raw` only when typed surface is missing.
195
+ - Do not hardcode HowOne API URLs.
196
+ - Do not pass owner fields for authenticated own records.
197
+ - Do not import or create SDK toast APIs.
198
+ - Do not remove HowOne floating logo unless explicitly requested.
199
+ - Do not call AI workflows from render.
200
+ - Do not persist workflow envelopes or UI-only fields.
201
+ - Do not assume unavailable AI capabilities.
85
202
 
86
- - Read manifests after sync, not before.
87
- - Preserve existing bindings unless the manifest changed.
88
- - Export explicit `Record`, `Create`, and `Update` types.
89
- - Do not include system fields in create/update payloads.
90
- - Do not infer public access from `visibility` alone; inspect `access.public`.
203
+ ## Final Architecture Checklist
91
204
 
92
- ## Common Failure Modes
205
+ Before writing final code:
93
206
 
94
- - Repeated login after refresh: app used `auth.isAuthenticated()` as first-load truth or stored
95
- token outside SDK. Read `03-sdk/03-auth.md`.
96
- - Data is not isolated: app used `query()` instead of `query.mine()` for `authenticated.read =
97
- "own"`, or wrote owner fields manually.
98
- - Public page 401/403: app used authenticated namespace for public content or schema does not
99
- allow public access.
100
- - Generated types drift from backend: app edited `src/lib/sdk.ts` from memory instead of synced
101
- manifest.
207
+ - Data posture is explicit.
208
+ - Auth mode is explicit.
209
+ - Public access has filters/sorts/scopes/limits.
210
+ - AI capability is supported by service catalog.
211
+ - Workflow count follows one-feature rule or RAG exception.
212
+ - Persistence is separate from AI workflow.
213
+ - Manifests are synced before SDK codegen.
214
+ - `src/lib/sdk.ts` is the only app SDK entrypoint.
215
+ - UI owns visible states and feedback.
@@ -13,6 +13,11 @@ HowOne apps are driven by two backend-synced manifests:
13
13
 
14
14
  Sync tools (`sync_schema_artifacts`, `sync_ai_artifacts`) write the manifests. The coding agent reads the manifests and writes `src/lib/sdk.ts`.
15
15
 
16
+ For AI capabilities, external workflow create/update is submitted by `external-ai-capability` from
17
+ the synced manifest. Do not duplicate AI schemas in app code beyond generated zod/type bindings.
18
+ For workflow edits, `workflowConfigID` belongs to the external workflow operation; it is not an SDK
19
+ binding field.
20
+
16
21
  ---
17
22
 
18
23
  ## Reading `.howone/database/manifest.json`
@@ -157,6 +162,7 @@ export type CommentUpdate = Partial<CommentCreate>
157
162
  {
158
163
  "id": "generateStory",
159
164
  "name": "Generate Story",
165
+ "workflowId": "d69ab648-2c00-4d94-928e-01bd7b2a5bb2",
160
166
  "inputSchema": {
161
167
  "type": "object",
162
168
  "properties": {
@@ -179,6 +185,7 @@ export type CommentUpdate = Partial<CommentCreate>
179
185
  {
180
186
  "id": "translateText",
181
187
  "name": "Translate Text",
188
+ "workflowId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
182
189
  "inputSchema": {
183
190
  "type": "object",
184
191
  "properties": {
@@ -219,12 +226,12 @@ export const generateStoryInputSchema = z.object({
219
226
  })
220
227
  export type GenerateStoryInput = z.infer<typeof generateStoryInputSchema>
221
228
 
222
- // Output type for manual unwrapping (do NOT put in defineAiAction as outputSchema)
223
- export type GenerateStoryOutput = {
224
- title: string
225
- content: string
226
- summary?: string
227
- }
229
+ export const generateStoryOutputSchema = z.object({
230
+ title: z.string(),
231
+ content: z.string(),
232
+ summary: z.string().optional(),
233
+ })
234
+ export type GenerateStoryOutput = z.infer<typeof generateStoryOutputSchema>
228
235
 
229
236
  // ── translateText ─────────────────────────────────────────────
230
237
  export const translateTextInputSchema = z.object({
@@ -235,6 +242,10 @@ export const translateTextInputSchema = z.object({
235
242
  export type TranslateTextInput = z.infer<typeof translateTextInputSchema>
236
243
  ```
237
244
 
245
+ Do not make required output fields optional to silence validation failures. Do not add
246
+ `.passthrough()` as a workaround for EAX execution envelopes. `defineAiAction` validates the
247
+ workflow `finalResult` payload when an `outputSchema` is configured.
248
+
238
249
  ---
239
250
 
240
251
  ## Full Generated `src/lib/sdk.ts`
@@ -249,6 +260,8 @@ import {
249
260
  defineAiAction,
250
261
  defineAiActions,
251
262
  defineEntities,
263
+ runAiActionAndPersist,
264
+ type EntityDefinition,
252
265
  type EntityRecord,
253
266
  withAiActions,
254
267
  withEntities,
@@ -293,6 +306,41 @@ export type CommentCreate = {
293
306
  }
294
307
  export type CommentUpdate = Partial<CommentCreate>
295
308
 
309
+ export const storyEntityDefinition = {
310
+ name: 'Story',
311
+ type: 'object',
312
+ properties: {
313
+ title: { type: 'string' },
314
+ content: { type: 'string' },
315
+ authorId: { type: 'string' },
316
+ status: { type: 'string', enum: ['draft', 'published', 'archived'] },
317
+ wordCount: { type: 'integer' },
318
+ tags: { type: 'array', items: { type: 'string' } },
319
+ coverUrl: { type: 'string' },
320
+ },
321
+ required: ['title', 'content', 'authorId', 'status', 'wordCount'],
322
+ access: {
323
+ authenticated: { read: 'own', create: 'own', update: 'own', delete: 'own' },
324
+ public: { read: 'none', create: 'none', update: 'none', delete: 'none' },
325
+ },
326
+ } satisfies EntityDefinition
327
+
328
+ export const commentEntityDefinition = {
329
+ name: 'Comment',
330
+ type: 'object',
331
+ properties: {
332
+ storyId: { type: 'string' },
333
+ authorId: { type: 'string' },
334
+ body: { type: 'string' },
335
+ likes: { type: 'integer' },
336
+ },
337
+ required: ['storyId', 'authorId', 'body'],
338
+ access: {
339
+ authenticated: { read: 'own', create: 'own', update: 'own', delete: 'own' },
340
+ public: { read: 'none', create: 'none', update: 'none', delete: 'none' },
341
+ },
342
+ } satisfies EntityDefinition
343
+
296
344
  // ═══════════════════════════════════════════════════════════════
297
345
  // AI SCHEMAS & TYPES
298
346
  // ═══════════════════════════════════════════════════════════════
@@ -303,11 +351,12 @@ export const generateStoryInputSchema = z.object({
303
351
  language: z.string().optional(),
304
352
  })
305
353
  export type GenerateStoryInput = z.infer<typeof generateStoryInputSchema>
306
- export type GenerateStoryOutput = {
307
- title: string
308
- content: string
309
- summary?: string
310
- }
354
+ export const generateStoryOutputSchema = z.object({
355
+ title: z.string(),
356
+ content: z.string(),
357
+ summary: z.string().optional(),
358
+ })
359
+ export type GenerateStoryOutput = z.infer<typeof generateStoryOutputSchema>
311
360
 
312
361
  export const translateTextInputSchema = z.object({
313
362
  text: z.string(),
@@ -315,10 +364,6 @@ export const translateTextInputSchema = z.object({
315
364
  formality: z.enum(['formal', 'informal']).optional(),
316
365
  })
317
366
  export type TranslateTextInput = z.infer<typeof translateTextInputSchema>
318
- export type TranslateTextOutput = {
319
- translatedText: string
320
- detectedLang?: string
321
- }
322
367
 
323
368
  // ═══════════════════════════════════════════════════════════════
324
369
  // CLIENT
@@ -344,9 +389,12 @@ export const entities = defineEntities({
344
389
 
345
390
  export const ai = defineAiActions({
346
391
  generateStory: defineAiAction('generateStory', {
392
+ workflowId: 'd69ab648-2c00-4d94-928e-01bd7b2a5bb2',
347
393
  inputSchema: generateStoryInputSchema,
394
+ outputSchema: generateStoryOutputSchema,
348
395
  }),
349
396
  translateText: defineAiAction('translateText', {
397
+ workflowId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
350
398
  inputSchema: translateTextInputSchema,
351
399
  }),
352
400
  })
@@ -366,12 +414,17 @@ export default howone
366
414
  Before finalising generated code, verify:
367
415
 
368
416
  - [ ] Every entity from `.howone/database/manifest.json` has a `Record`, `Create`, and `Update` type
417
+ - [ ] Entity definitions are exported as `*EntityDefinition` when app code needs payload/query guards
369
418
  - [ ] `Create` types are defined **explicitly** (not via `Omit`)
370
419
  - [ ] Create optionality accounts for `required`, `default`, `defaultValue`, `autoGenerate`, and nullable types
371
420
  - [ ] System fields are not present in create/update input types
372
421
  - [ ] Public query/write types are generated only from `access.public.allowedFilters`, `allowedSorts`, `requiredScopes`, and write permissions
373
422
  - [ ] Every AI action from `.howone/ai/manifest.json` has an `inputSchema` zod object
423
+ - [ ] Every AI action with manifest `outputSchema` has a matching zod `outputSchema`
424
+ - [ ] Every AI action binding includes the exact manifest `workflowId`
374
425
  - [ ] Required input fields are not `.optional()` in zod
426
+ - [ ] Required output fields are not `.optional()` in zod
427
+ - [ ] AI output schemas do not use `.passthrough()` to hide execution-envelope mismatches
375
428
  - [ ] AI action names match the manifest `id` exactly (case-sensitive)
376
429
  - [ ] `createClient` uses `import.meta.env.*` only
377
430
  - [ ] `withEntities` is applied before `withAiActions` in the composition chain
@@ -408,20 +461,21 @@ export const entities = defineEntities({
408
461
 
409
462
  When the app generates data with AI and saves it to an entity:
410
463
 
411
- 1. Read `.howone/ai/manifest.json` determine AI output shape.
412
- 2. Define entity fields that mirror the AI output fields.
413
- 3. Add any app-specific metadata fields (status, userId, createdAt, etc.).
414
- 4. Generate the entity type, then the AI action binding.
464
+ 1. Read `.howone/ai/manifest.json` to know the typed AI output.
465
+ 2. Decide which output fields are durable product fields.
466
+ 3. Add app-specific persistence fields such as `status`, `errorMessage`, `requestedAt`,
467
+ `completedAt`, source URLs, prompt/options, or share state.
468
+ 4. Define/update the entity schema from product persistence needs, not by blindly copying
469
+ `outputSchema`.
470
+ 5. Generate entity types and AI action bindings from synced manifests.
471
+ 6. Use `runAiActionAndPersist()` for history-style products.
415
472
 
416
473
  ```ts
417
474
  // AI generates: { title: string, content: string, summary: string }
418
475
  // Save it to Story entity which adds: authorId, status, wordCount
419
476
 
420
477
  async function generateAndSave(input: GenerateStoryInput, authorId: string) {
421
- const result = await howone.ai.generateStory.run(input)
422
- if (!result.success) throw new Error(result.errors.join(', '))
423
-
424
- const output = result.finalResult as GenerateStoryOutput
478
+ const output = await howone.ai.generateStory.run(input)
425
479
 
426
480
  return howone.entities.Story.create({
427
481
  title: output.title,
@@ -432,3 +486,24 @@ async function generateAndSave(input: GenerateStoryInput, authorId: string) {
432
486
  })
433
487
  }
434
488
  ```
489
+
490
+ History-style generation should create a pending record before running AI:
491
+
492
+ ```ts
493
+ await runAiActionAndPersist({
494
+ entity: howone.entities.Generation,
495
+ input,
496
+ createPending: (input) => ({
497
+ prompt: input.topic,
498
+ status: 'pending',
499
+ requestedAt: new Date().toISOString(),
500
+ }),
501
+ run: (input) => howone.ai.generateStory.run(input),
502
+ mapCompleted: ({ output }) => ({
503
+ status: 'completed',
504
+ title: output.title,
505
+ content: output.content,
506
+ completedAt: new Date().toISOString(),
507
+ }),
508
+ })
509
+ ```