howone 0.2.3 → 0.2.6

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 (23) hide show
  1. package/package.json +1 -1
  2. package/templates/vite/.howone/skills/howone/01-architect/01-app-generation.md +8 -7
  3. package/templates/vite/.howone/skills/howone/01-architect/02-manifest-codegen.md +121 -436
  4. package/templates/vite/.howone/skills/howone/03-ai-capabilities/04-workflow-operations.md +13 -4
  5. package/templates/vite/.howone/skills/howone/04-app-sdk/01-client-setup.md +94 -261
  6. package/templates/vite/.howone/skills/howone/04-app-sdk/02-entity-operations.md +85 -465
  7. package/templates/vite/.howone/skills/howone/04-app-sdk/03-auth.md +11 -7
  8. package/templates/vite/.howone/skills/howone/04-app-sdk/04-react-integration.md +84 -137
  9. package/templates/vite/.howone/skills/howone/04-app-sdk/05-file-upload.md +66 -273
  10. package/templates/vite/.howone/skills/howone/04-app-sdk/06-raw-http.md +72 -249
  11. package/templates/vite/.howone/skills/howone/04-app-sdk/07-ai-action-calls.md +135 -499
  12. package/templates/vite/.howone/skills/howone/04-app-sdk/08-ai-manifest-handoff.md +49 -196
  13. package/templates/vite/.howone/skills/howone/04-app-sdk/09-extension-boundaries.md +4 -4
  14. package/templates/vite/.howone/skills/howone/04-app-sdk/10-workflow-execute-sse.md +94 -61
  15. package/templates/vite/.howone/skills/howone/04-app-sdk/11-entity-data-access-patterns.md +4 -3
  16. package/templates/vite/.howone/skills/howone/SKILL.md +48 -8
  17. package/templates/vite/.howone/skills/howone/references/common-errors.md +27 -0
  18. package/templates/vite/.howone/skills/howone/references/version-evidence.md +47 -0
  19. package/templates/vite/.howone/skills/howone/scripts/verify-project.mjs +151 -0
  20. package/templates/vite/package.json +1 -1
  21. package/templates/vite/src/App.tsx +9 -5
  22. package/templates/vite/src/lib/sdk.ts +7 -5
  23. package/templates/vite/bun.lock +0 -1478
@@ -1,509 +1,194 @@
1
1
  # Manifest Codegen
2
2
 
3
- ## Overview
3
+ Generate `src/lib/sdk.ts` from the two synced manifests. Do not infer names, fields, access rules,
4
+ schemas, or workflow IDs from a prompt, old source file, or dependency cache.
4
5
 
5
- HowOne apps are driven by two backend-synced manifests:
6
+ ## Source and evidence order
6
7
 
7
- | File | Contents | Drives |
8
- |---|---|---|
9
- | `.howone/database/manifest.json` | Entity names, fields, types | Entity type definitions + `client.entity<...>` bindings |
10
- | `.howone/ai/manifest.json` | AI action IDs, input/output JSON schemas | zod schemas + `defineAiAction` bindings |
8
+ 1. Read `.howone/database/manifest.json` and `.howone/ai/manifest.json` after the latest sync.
9
+ 2. Read `package.json` and installed SDK declarations as described by
10
+ `../../references/version-evidence.md`.
11
+ 3. Load only the operation recipe needed by the app.
12
+ 4. Generate bindings, run the project verifier, then typecheck/build.
11
13
 
12
- **The coding agent should always generate `src/lib/sdk.ts` from these manifest files, not from memory or assumptions.**
14
+ If an AI workflow update may rotate `workflowId`, re-read the AI manifest immediately before writing
15
+ the binding. Never preserve a stale UUID merely to avoid a diff.
13
16
 
14
- Sync tools (`sync_schema_artifacts`, `sync_ai_artifacts`) write the manifests. The coding agent reads the manifests and writes `src/lib/sdk.ts`.
17
+ ## Database manifest mapping
15
18
 
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, `external-ai-capability` may rotate the manifest `workflowId`; always re-read
19
- `.howone/ai/manifest.json` after the tool returns before updating `src/lib/sdk.ts`.
19
+ For each entity, generate:
20
20
 
21
- ---
21
+ - `Record` type extending `EntityRecord` with only declared business fields;
22
+ - explicit `Create` type (never `Omit<Record, ...>`);
23
+ - `Update = Partial<Create>` unless the product needs a narrower update contract;
24
+ - a `defineEntityDefinition(...)` constant that preserves `properties`, `required`, and the entire
25
+ `access` block;
26
+ - an authenticated binding wrapped with `withEntityContract`;
27
+ - a public binding only when `access.public` exposes at least one operation, wrapped with
28
+ `withPublicEntityContract`.
22
29
 
23
- ## Reading `.howone/database/manifest.json`
30
+ ### Type mapping
24
31
 
25
- ### Example manifest
26
-
27
- ```json
28
- {
29
- "version": "1",
30
- "entities": [
31
- {
32
- "name": "Story",
33
- "fields": [
34
- { "name": "title", "type": "string", "required": true },
35
- { "name": "content", "type": "text", "required": true },
36
- { "name": "authorId", "type": "string", "required": true },
37
- { "name": "status", "type": "string", "required": true, "enum": ["draft", "published", "archived"] },
38
- { "name": "wordCount", "type": "integer", "required": true },
39
- { "name": "tags", "type": "array", "items": "string", "required": false },
40
- { "name": "coverUrl", "type": "string", "required": false }
41
- ]
42
- },
43
- {
44
- "name": "Comment",
45
- "fields": [
46
- { "name": "storyId", "type": "string", "required": true },
47
- { "name": "authorId", "type": "string", "required": true },
48
- { "name": "body", "type": "text", "required": true },
49
- { "name": "likes", "type": "integer", "required": false }
50
- ]
51
- }
52
- ]
53
- }
54
- ```
55
-
56
- ### Field type → TypeScript type mapping
57
-
58
- | Manifest type | TypeScript type |
32
+ | Manifest field type | TypeScript/Zod |
59
33
  |---|---|
60
- | `string` | `string` |
61
- | `text` | `string` |
62
- | `integer` | `number` |
63
- | `number` / `float` | `number` |
64
- | `boolean` | `boolean` |
65
- | `date` / `datetime` | `string` (ISO 8601) |
66
- | `array` (items: string) | `string[]` |
67
- | `array` (items: object) | `Record<string, unknown>[]` or inline type |
68
- | `object` | `Record<string, unknown>` |
69
- | `enum` | `'value1' \| 'value2' \| ...` |
70
- | `["string", "null"]` | `string \| null` |
71
-
72
- - Fields in `required: true` are non-optional in `Record` types.
73
- - In `Create` types, required fields are required only when they do not have `default` or `autoGenerate`.
74
- - Fields with `default`, `defaultValue`, or `autoGenerate` are optional in `Create`.
75
- - Nullable fields include `null`.
76
- - System response fields are never generated into `Create` or `Update`.
77
-
78
- ### Access-aware generated helpers
79
-
80
- Use the manifest `access` block to decide which namespace UI code should call:
81
-
82
- ```ts
83
- export type ArticlePublicQuery = {
84
- published?: boolean
85
- category?: string
86
- slug?: string
87
- page?: { number?: number; size?: number }
88
- orderBy?: { publishedAt?: 'asc' | 'desc'; updatedDate?: 'asc' | 'desc' }
89
- }
90
-
91
- const publicArticles = await howone.public.entities.Article.query({
92
- published: true,
93
- orderBy: { publishedAt: 'desc' },
94
- })
95
- ```
96
-
97
- Rules:
98
-
99
- - `access.authenticated.*` drives `howone.entities.*`.
100
- - `access.public.read = "list"` allows `howone.public.entities.Entity.query`.
101
- - `access.public.read = "scoped"` requires `queryScoped` / `query.scoped` and all `requiredScopes`.
102
- - Public query types must include only `allowedFilters`, `allowedSorts`, `page`, `limit`, `search`, `include`, and `exactCount`.
103
- - Public create/update types should only be emitted when `access.public.create/update` is not `"none"`.
104
- - Public create must include `created_by_user_id` when the schema requires public owner assignment.
105
-
106
- ### Generated TypeScript from the example manifest
107
-
108
- ```ts
109
- import { type EntityRecord } from '@howone/sdk'
110
-
111
- // ── Story ─────────────────────────────────────────────────────
112
- export type StoryRecord = EntityRecord & {
113
- title: string
114
- content: string
115
- authorId: string
116
- status: 'draft' | 'published' | 'archived'
117
- wordCount: number
118
- tags?: string[]
119
- coverUrl?: string
120
- }
121
-
122
- export type StoryCreate = {
123
- title: string
124
- content: string
125
- authorId: string
126
- status: 'draft' | 'published' | 'archived'
127
- wordCount: number
128
- tags?: string[]
129
- coverUrl?: string
130
- }
34
+ | `string`, `text` | `string` / `z.string()` |
35
+ | `integer`, `number`, `float` | `number` / `z.number()` (`.int()` for integer) |
36
+ | `boolean` | `boolean` / `z.boolean()` |
37
+ | `date`, `datetime` | ISO `string` / `z.string()` (add `.datetime()` only when the contract guarantees it) |
38
+ | `array` | `T[]` / `z.array(...)` |
39
+ | `object` | explicit object type / `z.object(...)` |
40
+ | enum | literal union / `z.enum([...])` |
41
+ | nullable union | `T \| null` / `z.nullable(...)` |
131
42
 
132
- export type StoryUpdate = Partial<StoryCreate>
43
+ Fields with `default`, `defaultValue`, or `autoGenerate` can be optional in `Create` even when
44
+ listed as required by the response schema. Never include system fields (`id`, timestamps, schema
45
+ version) or ownership fields in Create/Update types.
133
46
 
134
- // ── Comment ───────────────────────────────────────────────────
135
- export type CommentRecord = EntityRecord & {
136
- storyId: string
137
- authorId: string
138
- body: string
139
- likes?: number
140
- }
47
+ ## AI manifest mapping
141
48
 
142
- export type CommentCreate = {
143
- storyId: string
144
- authorId: string
145
- body: string
146
- likes?: number
147
- }
49
+ For each action:
148
50
 
149
- export type CommentUpdate = Partial<CommentCreate>
150
- ```
51
+ - preserve the manifest action `id` exactly (case-sensitive);
52
+ - convert `inputSchema` and `outputSchema` to Zod;
53
+ - preserve required arrays and closed enums;
54
+ - include the exact UUID `workflowId` in `defineAiAction`;
55
+ - never add `.passthrough()` to hide an envelope mismatch;
56
+ - never make required output fields optional to make validation pass.
151
57
 
152
- ---
153
-
154
- ## Reading `.howone/ai/manifest.json`
155
-
156
- ### Example manifest
157
-
158
- ```json
159
- {
160
- "version": "1",
161
- "actions": [
162
- {
163
- "id": "generateStory",
164
- "name": "Generate Story",
165
- "workflowId": "d69ab648-2c00-4d94-928e-01bd7b2a5bb2",
166
- "inputSchema": {
167
- "type": "object",
168
- "properties": {
169
- "topic": { "type": "string" },
170
- "ageRange": { "type": "string", "enum": ["3-5", "6-8", "9-12"] },
171
- "language": { "type": "string" }
172
- },
173
- "required": ["topic", "ageRange"]
174
- },
175
- "outputSchema": {
176
- "type": "object",
177
- "properties": {
178
- "title": { "type": "string" },
179
- "content": { "type": "string" },
180
- "summary": { "type": "string" }
181
- },
182
- "required": ["title", "content"]
183
- }
184
- },
185
- {
186
- "id": "translateText",
187
- "name": "Translate Text",
188
- "workflowId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
189
- "inputSchema": {
190
- "type": "object",
191
- "properties": {
192
- "text": { "type": "string" },
193
- "targetLang": { "type": "string" },
194
- "formality": { "type": "string", "enum": ["formal", "informal"] }
195
- },
196
- "required": ["text", "targetLang"]
197
- }
198
- }
199
- ]
200
- }
201
- ```
202
-
203
- ### JSON Schema → zod mapping
58
+ `defineAiAction` validates workflow IDs during module initialization. Missing or invalid values are
59
+ configuration failures, not a runtime fallback case.
204
60
 
205
- | JSON Schema | zod |
206
- |---|---|
207
- | `{ "type": "string" }` | `z.string()` |
208
- | `{ "type": "number" }` | `z.number()` |
209
- | `{ "type": "integer" }` | `z.number().int()` |
210
- | `{ "type": "boolean" }` | `z.boolean()` |
211
- | `{ "type": "string", "enum": ["a","b"] }` | `z.enum(['a', 'b'])` |
212
- | `{ "type": "array", "items": { "type": "string" } }` | `z.array(z.string())` |
213
- | `{ "type": "object", "properties": { ... } }` | `z.object({ ... })` |
214
- | Field NOT in `required[]` | append `.optional()` |
61
+ ## Generated shape
215
62
 
216
- ### Generated zod schemas from the example manifest
217
-
218
- ```ts
219
- import { z } from 'zod'
220
-
221
- // ── generateStory ─────────────────────────────────────────────
222
- export const generateStoryInputSchema = z.object({
223
- topic: z.string(),
224
- ageRange: z.enum(['3-5', '6-8', '9-12']),
225
- language: z.string().optional(),
226
- })
227
- export type GenerateStoryInput = z.infer<typeof generateStoryInputSchema>
228
-
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>
235
-
236
- // ── translateText ─────────────────────────────────────────────
237
- export const translateTextInputSchema = z.object({
238
- text: z.string(),
239
- targetLang: z.string(),
240
- formality: z.enum(['formal', 'informal']).optional(),
241
- })
242
- export type TranslateTextInput = z.infer<typeof translateTextInputSchema>
243
- ```
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
-
249
- ---
250
-
251
- ## Full Generated `src/lib/sdk.ts`
252
-
253
- Combining both manifests from the examples above:
63
+ This is the architecture pattern; replace every placeholder with manifest-derived code:
254
64
 
255
65
  ```ts
256
66
  // src/lib/sdk.ts
257
- // Generated from .howone/database/manifest.json and .howone/ai/manifest.json
258
67
  import {
259
68
  createClient,
260
69
  defineAiAction,
261
70
  defineAiActions,
71
+ defineEntityDefinition,
262
72
  defineEntities,
263
- runAiActionAndPersist,
73
+ definePublicEntities,
264
74
  type EntityDefinition,
265
75
  type EntityRecord,
266
76
  withAiActions,
267
77
  withEntities,
78
+ withEntityContract,
79
+ withPublicEntities,
80
+ withPublicEntityContract,
268
81
  } from '@howone/sdk'
269
82
  import { z } from 'zod'
270
83
 
271
- // ═══════════════════════════════════════════════════════════════
272
- // ENTITY TYPES
273
- // ═══════════════════════════════════════════════════════════════
274
-
275
- export type StoryRecord = EntityRecord & {
276
- title: string
277
- content: string
278
- authorId: string
279
- status: 'draft' | 'published' | 'archived'
280
- wordCount: number
281
- tags?: string[]
282
- coverUrl?: string
283
- }
284
- export type StoryCreate = {
84
+ export type ArticleRecord = EntityRecord & {
285
85
  title: string
286
- content: string
287
- authorId: string
288
- status: 'draft' | 'published' | 'archived'
289
- wordCount: number
290
- tags?: string[]
291
- coverUrl?: string
86
+ status: 'draft' | 'published'
292
87
  }
293
- export type StoryUpdate = Partial<StoryCreate>
88
+ export type ArticleCreate = { title: string; status: 'draft' | 'published' }
89
+ export type ArticleUpdate = Partial<ArticleCreate>
294
90
 
295
- export type CommentRecord = EntityRecord & {
296
- storyId: string
297
- authorId: string
298
- body: string
299
- likes?: number
300
- }
301
- export type CommentCreate = {
302
- storyId: string
303
- authorId: string
304
- body: string
305
- likes?: number
306
- }
307
- export type CommentUpdate = Partial<CommentCreate>
308
-
309
- export const storyEntityDefinition = {
310
- name: 'Story',
91
+ export const articleEntityDefinition = defineEntityDefinition({
92
+ name: 'Article',
311
93
  type: 'object',
312
94
  properties: {
313
95
  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' },
96
+ status: { type: 'string', enum: ['draft', 'published'] },
336
97
  },
337
- required: ['storyId', 'authorId', 'body'],
98
+ required: ['title', 'status'],
338
99
  access: {
339
- authenticated: { read: 'own', create: 'own', update: 'own', delete: 'own' },
340
- public: { read: 'none', create: 'none', update: 'none', delete: 'none' },
100
+ authenticated: { read: 'all', create: 'all', update: 'all', delete: 'all' },
101
+ public: {
102
+ read: 'list',
103
+ create: 'none',
104
+ update: 'none',
105
+ allowedFilters: ['status'],
106
+ allowedSorts: ['createdDate'],
107
+ },
341
108
  },
342
- } satisfies EntityDefinition
343
-
344
- // ═══════════════════════════════════════════════════════════════
345
- // AI SCHEMAS & TYPES
346
- // ═══════════════════════════════════════════════════════════════
347
-
348
- export const generateStoryInputSchema = z.object({
349
- topic: z.string(),
350
- ageRange: z.enum(['3-5', '6-8', '9-12']),
351
- language: z.string().optional(),
352
- })
353
- export type GenerateStoryInput = z.infer<typeof generateStoryInputSchema>
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>
360
-
361
- export const translateTextInputSchema = z.object({
362
- text: z.string(),
363
- targetLang: z.string(),
364
- formality: z.enum(['formal', 'informal']).optional(),
365
- })
366
- export type TranslateTextInput = z.infer<typeof translateTextInputSchema>
367
-
368
- // ═══════════════════════════════════════════════════════════════
369
- // CLIENT
370
- // ═══════════════════════════════════════════════════════════════
109
+ } satisfies EntityDefinition)
371
110
 
372
111
  const client = createClient({
373
112
  projectId: import.meta.env.VITE_HOWONE_PROJECT_ID,
374
113
  env: import.meta.env.VITE_HOWONE_ENV,
375
114
  })
376
115
 
377
- // ═══════════════════════════════════════════════════════════════
378
- // ENTITY BINDINGS
379
- // ═══════════════════════════════════════════════════════════════
380
-
381
116
  export const entities = defineEntities({
382
- Story: client.entity<StoryRecord, StoryCreate, StoryUpdate>('Story'),
383
- Comment: client.entity<CommentRecord, CommentCreate, CommentUpdate>('Comment'),
117
+ Article: withEntityContract(
118
+ client.entity<ArticleRecord, ArticleCreate, ArticleUpdate>('Article'),
119
+ articleEntityDefinition,
120
+ ),
384
121
  })
385
122
 
386
- // ═══════════════════════════════════════════════════════════════
387
- // AI ACTION BINDINGS
388
- // ═══════════════════════════════════════════════════════════════
123
+ // Include only manifest-approved public entities/capabilities.
124
+ export const publicEntities = definePublicEntities({
125
+ Article: withPublicEntityContract(
126
+ client.public.entity<ArticleRecord, ArticleCreate, ArticleUpdate>('Article'),
127
+ articleEntityDefinition,
128
+ ),
129
+ })
130
+
131
+ const summarizeInputSchema = z.object({ text: z.string() })
132
+ const summarizeOutputSchema = z.object({ summary: z.string() })
133
+ export type SummarizeInput = z.infer<typeof summarizeInputSchema>
134
+ export type SummarizeOutput = z.infer<typeof summarizeOutputSchema>
389
135
 
390
136
  export const ai = defineAiActions({
391
- generateStory: defineAiAction('generateStory', {
137
+ summarize: defineAiAction('summarize', {
392
138
  workflowId: 'd69ab648-2c00-4d94-928e-01bd7b2a5bb2',
393
- inputSchema: generateStoryInputSchema,
394
- outputSchema: generateStoryOutputSchema,
395
- }),
396
- translateText: defineAiAction('translateText', {
397
- workflowId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
398
- inputSchema: translateTextInputSchema,
139
+ inputSchema: summarizeInputSchema,
140
+ outputSchema: summarizeOutputSchema,
399
141
  }),
400
142
  })
401
143
 
402
- // ═══════════════════════════════════════════════════════════════
403
- // COMPOSED CLIENT
404
- // ═══════════════════════════════════════════════════════════════
405
-
406
- const howone = withAiActions(withEntities(client, entities), ai)
144
+ const howoneWithEntities = withEntities(client, entities)
145
+ const howoneWithPublicEntities = withPublicEntities(howoneWithEntities, publicEntities)
146
+ const howone = withAiActions(howoneWithPublicEntities, ai)
407
147
  export default howone
408
148
  ```
409
149
 
410
- ---
411
-
412
- ## Codegen Checklist
413
-
414
- Before finalising generated code, verify:
150
+ If the manifest has no public capability, omit `publicEntities` and `withPublicEntities`. If an
151
+ entity has `access.authenticated.create: 'none'`, the authenticated wrapper has no `create` or
152
+ `bulkCreate` property at both runtime and type level. Do not cast it back.
415
153
 
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
418
- - [ ] `Create` types are defined **explicitly** (not via `Omit`)
419
- - [ ] Create optionality accounts for `required`, `default`, `defaultValue`, `autoGenerate`, and nullable types
420
- - [ ] System fields are not present in create/update input types
421
- - [ ] Public query/write types are generated only from `access.public.allowedFilters`, `allowedSorts`, `requiredScopes`, and write permissions
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`
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
428
- - [ ] AI action names match the manifest `id` exactly (case-sensitive)
429
- - [ ] `createClient` uses `import.meta.env.*` only
430
- - [ ] `withEntities` is applied before `withAiActions` in the composition chain
431
- - [ ] No generated source files are placed under `.howone/`
432
- - [ ] Exported types and schemas are importable from `@/lib/sdk`
154
+ ## Query and access generation
433
155
 
434
- ---
435
-
436
- ## Incremental Update Pattern
437
-
438
- When new entities or actions are added to the manifests, update `src/lib/sdk.ts` by:
439
-
440
- 1. Reading the current `src/lib/sdk.ts` to preserve existing bindings and import style.
441
- 2. Appending new types, schemas, entity bindings, and action bindings.
442
- 3. Not removing existing bindings unless the manifest explicitly removed them.
443
- 4. Preserving export names for backward compatibility with existing UI code.
156
+ Authenticated code uses `howone.entities.Entity`; public code uses `howone.public.entities.Entity`.
157
+ Generate `QueryOptions` with `where`, `page`, `orderBy`, `include`, and `exactCount` only. Public
158
+ filters and sorts must be limited to manifest `allowedFilters`/`allowedSorts`; scoped reads must
159
+ provide every `requiredScopes` field under `where`.
444
160
 
445
161
  ```ts
446
- // Before (existing)
447
- export const entities = defineEntities({
448
- Story: client.entity<StoryRecord, StoryCreate, StoryUpdate>('Story'),
449
- })
450
-
451
- // After (added Comment entity from new manifest)
452
- export const entities = defineEntities({
453
- Story: client.entity<StoryRecord, StoryCreate, StoryUpdate>('Story'),
454
- Comment: client.entity<CommentRecord, CommentCreate, CommentUpdate>('Comment'),
162
+ await howone.public.entities.Article.query({
163
+ where: { status: 'published' },
164
+ orderBy: { createdDate: 'desc' },
165
+ page: { number: 1, size: 20 },
455
166
  })
456
167
  ```
457
168
 
458
- ---
169
+ Never put `created_by_id`, `ownerId`, `puid`, or system fields into authenticated or public payloads.
170
+ Ownership comes from auth or the public scope contract.
459
171
 
460
- ## AI-First Persistence Pattern
172
+ ## Incremental changes
461
173
 
462
- When the app generates data with AI and saves it to an entity:
174
+ When a manifest changes:
463
175
 
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.
176
+ 1. re-read the complete current manifest;
177
+ 2. preserve app exports that are still present;
178
+ 3. add/remove bindings to match the manifest exactly;
179
+ 4. regenerate affected Zod and entity definitions;
180
+ 5. run the verifier and typecheck.
472
181
 
473
- ```ts
474
- // AI generates: { title: string, content: string, summary: string }
475
- // Save it to Story entity which adds: authorId, status, wordCount
476
-
477
- async function generateAndSave(input: GenerateStoryInput, authorId: string) {
478
- const output = await howone.ai.generateStory.run(input)
479
-
480
- return howone.entities.Story.create({
481
- title: output.title,
482
- content: output.content,
483
- authorId,
484
- status: 'draft',
485
- wordCount: output.content.split(' ').length,
486
- })
487
- }
488
- ```
182
+ Do not hand-edit `.howone/*` metadata. Do not place generated source files under `.howone/`.
489
183
 
490
- History-style generation should create a pending record before running AI:
184
+ ## Codegen checklist
491
185
 
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
- ```
186
+ - [ ] Every current entity has Record/Create/Update types and a definition constant.
187
+ - [ ] System/ownership fields are absent from write types.
188
+ - [ ] Access wrappers match authenticated/public operations.
189
+ - [ ] Public bindings include only manifest-approved entities.
190
+ - [ ] Query fields are typed from the generated Record, not `Record<string, unknown>`.
191
+ - [ ] Every AI action has a real Zod input schema and exact manifest UUID.
192
+ - [ ] Output requiredness and enums match the manifest.
193
+ - [ ] Provider consumes the composed `howone` client.
194
+ - [ ] `node .howone/skills/howone/scripts/verify-project.mjs` passes.