howone 0.1.23 → 0.1.26

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 (36) hide show
  1. package/package.json +1 -1
  2. package/templates/vite/.howone/skills/howone/01-architect/01-app-generation.md +215 -0
  3. package/templates/vite/.howone/skills/{howone-sdk → howone}/01-architect/02-manifest-codegen.md +67 -4
  4. package/templates/vite/.howone/skills/howone/02-database/01-schema-design.md +541 -0
  5. package/templates/vite/.howone/skills/howone/02-database/02-schema-operations.md +398 -0
  6. package/templates/vite/.howone/skills/howone/02-database/03-data-access-patterns.md +309 -0
  7. package/templates/vite/.howone/skills/howone/02-database/04-query-dsl-and-responses.md +237 -0
  8. package/templates/vite/.howone/skills/howone/02-database/05-ai-persistence-patterns.md +372 -0
  9. package/templates/vite/.howone/skills/{howone-sdk → howone}/03-sdk/01-client-setup.md +58 -36
  10. package/templates/vite/.howone/skills/{howone-sdk → howone}/03-sdk/02-entity-operations.md +67 -0
  11. package/templates/vite/.howone/skills/howone/03-sdk/03-auth.md +414 -0
  12. package/templates/vite/.howone/skills/howone/03-sdk/04-react-integration.md +191 -0
  13. package/templates/vite/.howone/skills/{howone-sdk → howone}/03-sdk/07-ai-action-calls.md +168 -64
  14. package/templates/vite/.howone/skills/howone/03-sdk/08-extension-boundaries.md +226 -0
  15. package/templates/vite/.howone/skills/howone/04-ai/01-ai-capability-architecture.md +205 -0
  16. package/templates/vite/.howone/skills/howone/04-ai/02-workflow-contract-rules.md +426 -0
  17. package/templates/vite/.howone/skills/howone/04-ai/03-ai-sdk-handoff.md +234 -0
  18. package/templates/vite/.howone/skills/howone/04-ai/04-service-capability-catalog.md +281 -0
  19. package/templates/vite/.howone/skills/howone/04-ai/05-workflow-operations.md +256 -0
  20. package/templates/vite/.howone/skills/howone/04-ai/06-ai-feature-playbooks.md +296 -0
  21. package/templates/vite/.howone/skills/{howone-sdk → howone}/SKILL.md +29 -12
  22. package/templates/vite/.howone/skills/howone/agents/openai.yaml +4 -0
  23. package/templates/vite/package.json +1 -1
  24. package/templates/vite/.howone/skills/howone-sdk/01-architect/01-app-generation.md +0 -126
  25. package/templates/vite/.howone/skills/howone-sdk/02-database/01-schema-design.md +0 -147
  26. package/templates/vite/.howone/skills/howone-sdk/02-database/02-schema-operations.md +0 -96
  27. package/templates/vite/.howone/skills/howone-sdk/02-database/03-data-access-patterns.md +0 -172
  28. package/templates/vite/.howone/skills/howone-sdk/03-sdk/03-auth.md +0 -616
  29. package/templates/vite/.howone/skills/howone-sdk/03-sdk/04-react-integration.md +0 -398
  30. package/templates/vite/.howone/skills/howone-sdk/04-ai/.gitkeep +0 -1
  31. package/templates/vite/.howone/skills/howone-sdk/04-ai/01-ai-capability-architecture.md +0 -142
  32. package/templates/vite/.howone/skills/howone-sdk/04-ai/02-workflow-contract-rules.md +0 -169
  33. package/templates/vite/.howone/skills/howone-sdk/04-ai/03-ai-sdk-handoff.md +0 -80
  34. package/templates/vite/.howone/skills/howone-sdk/agents/openai.yaml +0 -4
  35. /package/templates/vite/.howone/skills/{howone-sdk → howone}/03-sdk/05-file-upload.md +0 -0
  36. /package/templates/vite/.howone/skills/{howone-sdk → howone}/03-sdk/06-raw-http.md +0 -0
@@ -0,0 +1,398 @@
1
+ # Schema Operations
2
+
3
+ Use this reference when applying backend entity schema changes through HowOne runtime tools or
4
+ `client.schema.*`. It answers: **how do I safely change the schema and keep app code in sync?**
5
+
6
+ For schema design decisions, read `01-schema-design.md` first.
7
+
8
+ ## Source Of Truth
9
+
10
+ ```text
11
+ agent proposal = draft
12
+ backend preview/apply result = validated contract
13
+ synced .howone/database files = local copy of backend version
14
+ src/lib/sdk.ts = generated app binding from synced manifest
15
+ frontend code = consumer of generated bindings
16
+ ```
17
+
18
+ Do not hand-write `.howone/database/*`. Sync artifacts from a concrete backend version.
19
+
20
+ ## Preferred Patch Flow
21
+
22
+ Use patch flow for most feature-level changes:
23
+
24
+ 1. Inspect current `schema.getState()` and current entity definitions.
25
+ 2. Design a full patch containing all related operations.
26
+ 3. Preview the patch.
27
+ 4. Review risk, diff, and current version.
28
+ 5. Apply the exact same patch with `expectedVersionId`.
29
+ 6. Sync schema artifacts from `next.versionId`.
30
+ 7. Read `.howone/database/manifest.json`.
31
+ 8. Regenerate/update `src/lib/sdk.ts` bindings.
32
+ 9. Update frontend calls according to `access`.
33
+ 10. Run typecheck/build/tests.
34
+
35
+ Do not preview one patch and apply a different operation set.
36
+
37
+ ## SDK Schema Client
38
+
39
+ ```ts
40
+ const state = await client.schema.getState()
41
+ const definitions = await client.schema.listDefinitions()
42
+ const todo = await client.schema.getDefinition('Todo')
43
+
44
+ const preview = await client.schema.previewPatch(patch, {
45
+ expectedVersionId: state.currentVersionId,
46
+ reason: 'Add priority field to Todo',
47
+ })
48
+
49
+ if (preview.risk?.level === 'dangerous') {
50
+ // stop and ask for confirmation
51
+ }
52
+
53
+ const applied = await client.schema.applyPatch(patch, {
54
+ expectedVersionId: state.currentVersionId,
55
+ reason: 'Add priority field to Todo',
56
+ })
57
+ ```
58
+
59
+ Available schema methods:
60
+
61
+ | Method | Use |
62
+ |---|---|
63
+ | `listDefinitions()` | Get current entity definitions. |
64
+ | `getDefinition(entityName)` | Inspect one entity. |
65
+ | `upsertDefinitions(definition | definition[])` | Direct definition upsert; prefer patch for agent changes. |
66
+ | `operate(operation)` | Single operation endpoint. |
67
+ | `previewPatch(patch, options)` | Risk/diff preview without applying. |
68
+ | `applyPatch(patch, options)` | Apply versioned patch. |
69
+ | `getState()` | Current schema version pointer. |
70
+ | `listVersions()` | Version history. |
71
+ | `getVersion(versionId)` | Inspect version manifest. |
72
+ | `restore(versionId, reason?)` | Restore definitions by creating a new restore version. |
73
+
74
+ ## Operation Types
75
+
76
+ ```ts
77
+ type EntitySchemaOperationType =
78
+ | 'list_entities'
79
+ | 'get_entity'
80
+ | 'create_entity'
81
+ | 'update_entity'
82
+ | 'delete_entity'
83
+ | 'add_field'
84
+ | 'update_field'
85
+ | 'delete_field'
86
+ | 'set_field_required'
87
+ | 'unset_field_required'
88
+ ```
89
+
90
+ Operation shape:
91
+
92
+ ```ts
93
+ type EntitySchemaOperation = {
94
+ type: EntitySchemaOperationType
95
+ entityName?: string
96
+ payload?: Record<string, unknown>
97
+ }
98
+ ```
99
+
100
+ Patch shape:
101
+
102
+ ```ts
103
+ type EntitySchemaPatch = {
104
+ operations: EntitySchemaOperation[]
105
+ }
106
+ ```
107
+
108
+ ## Operation Payloads
109
+
110
+ ### create_entity
111
+
112
+ Use when the entity does not exist.
113
+
114
+ ```json
115
+ {
116
+ "type": "create_entity",
117
+ "entityName": "Todo",
118
+ "payload": {
119
+ "definition": {
120
+ "name": "Todo",
121
+ "type": "object",
122
+ "visibility": "private",
123
+ "properties": {
124
+ "text": { "type": "string" },
125
+ "completed": { "type": "boolean", "default": false }
126
+ },
127
+ "required": ["text"],
128
+ "access": {
129
+ "authenticated": { "read": "own", "create": "own", "update": "own", "delete": "own" },
130
+ "public": { "read": "none", "create": "none", "update": "none", "delete": "none" }
131
+ }
132
+ }
133
+ }
134
+ }
135
+ ```
136
+
137
+ Rules:
138
+
139
+ - `payload.definition.properties` is required.
140
+ - `required` must reference existing fields.
141
+ - Include explicit `access`, even if `visibility` seems obvious.
142
+
143
+ ### update_entity
144
+
145
+ Use for metadata and contract sections, not individual field changes.
146
+
147
+ ```json
148
+ {
149
+ "type": "update_entity",
150
+ "entityName": "Article",
151
+ "payload": {
152
+ "patch": {
153
+ "access": {
154
+ "authenticated": { "read": "all", "create": "all", "update": "all", "delete": "all" },
155
+ "public": {
156
+ "read": "list",
157
+ "create": "none",
158
+ "update": "none",
159
+ "delete": "none",
160
+ "allowedFilters": ["status", "slug"],
161
+ "allowedSorts": ["publishedAt"],
162
+ "defaultLimit": 20,
163
+ "maxLimit": 100
164
+ }
165
+ },
166
+ "performance": {
167
+ "defaultLimit": 20,
168
+ "maxLimit": 100,
169
+ "allowedSorts": ["publishedAt", "updatedDate"]
170
+ }
171
+ }
172
+ }
173
+ }
174
+ ```
175
+
176
+ Use this for:
177
+
178
+ - `description`
179
+ - `visibility`
180
+ - `access`
181
+ - `indexes`
182
+ - `relations`
183
+ - `presentation`
184
+ - `lifecycle`
185
+ - `performance`
186
+
187
+ ### add_field
188
+
189
+ ```json
190
+ {
191
+ "type": "add_field",
192
+ "entityName": "Todo",
193
+ "payload": {
194
+ "fieldName": "priority",
195
+ "field": {
196
+ "type": "string",
197
+ "enum": ["low", "medium", "high"],
198
+ "default": "medium"
199
+ },
200
+ "required": false
201
+ }
202
+ }
203
+ ```
204
+
205
+ Rules:
206
+
207
+ - If adding a required field to an existing entity, prefer a default.
208
+ - If no default exists and records already exist, treat as risky and ask for migration policy.
209
+ - Add indexes only when new query paths need them.
210
+
211
+ ### update_field
212
+
213
+ ```json
214
+ {
215
+ "type": "update_field",
216
+ "entityName": "Todo",
217
+ "payload": {
218
+ "fieldName": "priority",
219
+ "patch": {
220
+ "enum": ["low", "medium", "high", "urgent"]
221
+ }
222
+ }
223
+ }
224
+ ```
225
+
226
+ Risk levels:
227
+
228
+ - Adding enum values: usually safe.
229
+ - Removing enum values: risky if existing records use them.
230
+ - Changing type: high risk.
231
+ - Making nullable field required: high risk.
232
+ - Removing default: risky for create flows.
233
+
234
+ ### delete_field
235
+
236
+ ```json
237
+ {
238
+ "type": "delete_field",
239
+ "entityName": "Todo",
240
+ "payload": {
241
+ "fieldName": "legacyTag",
242
+ "removeFromData": false
243
+ }
244
+ }
245
+ ```
246
+
247
+ Rules:
248
+
249
+ - Default `removeFromData` to `false`.
250
+ - Only use `removeFromData: true` after explicit confirmation.
251
+ - Removing from schema does not necessarily remove historical data.
252
+
253
+ ### set_field_required / unset_field_required
254
+
255
+ ```json
256
+ {
257
+ "type": "set_field_required",
258
+ "entityName": "Todo",
259
+ "payload": { "fieldName": "text" }
260
+ }
261
+ ```
262
+
263
+ Rules:
264
+
265
+ - Required fields must exist in `properties`.
266
+ - Setting required on an existing field is risky unless a default exists or old records are acceptable.
267
+ - Unsetting required is generally safe but may change frontend validation expectations.
268
+
269
+ ### delete_entity
270
+
271
+ ```json
272
+ {
273
+ "type": "delete_entity",
274
+ "entityName": "Todo",
275
+ "payload": {
276
+ "hard": false,
277
+ "deleteData": false
278
+ }
279
+ }
280
+ ```
281
+
282
+ Rules:
283
+
284
+ - Default to soft delete.
285
+ - Hard delete requires explicit user request.
286
+ - `deleteData: true` requires explicit confirmation because it destroys records.
287
+
288
+ ## Risk Checklist
289
+
290
+ Stop and ask before applying when:
291
+
292
+ - deleting an entity;
293
+ - deleting a field used by UI or workflow output;
294
+ - removing historical data;
295
+ - changing field type;
296
+ - making a field required without default;
297
+ - broadening public access;
298
+ - enabling public write;
299
+ - reducing public guardrails such as removing required scopes or raising max limits;
300
+ - changing owner/public scope semantics.
301
+
302
+ Usually safe:
303
+
304
+ - adding optional field;
305
+ - adding field with default;
306
+ - adding enum value;
307
+ - adding index for existing query path;
308
+ - adding presentation metadata;
309
+ - adding stricter public filter/sort limits.
310
+
311
+ ## Version Semantics
312
+
313
+ Schema versions manage definitions, not business records.
314
+
315
+ Restore behavior:
316
+
317
+ ```text
318
+ restore(versionId)
319
+ -> creates a new current schema version from old manifest
320
+ -> future create/update uses restored definitions
321
+ -> old entitydatashares records are not rolled back
322
+ ```
323
+
324
+ Data records may still carry old fields after schema changes. Do not assume restore deletes or
325
+ rewrites data.
326
+
327
+ ## Manifest Sync Handoff
328
+
329
+ After apply, the result should include a version hint:
330
+
331
+ ```json
332
+ {
333
+ "next": {
334
+ "recommendedAction": "sync_schema_artifacts",
335
+ "versionId": "dbv_next"
336
+ }
337
+ }
338
+ ```
339
+
340
+ Agent handoff sequence:
341
+
342
+ 1. Sync artifacts for `versionId`.
343
+ 2. Read `.howone/database/manifest.json`.
344
+ 3. Update `src/lib/sdk.ts` generated entity bindings.
345
+ 4. Update frontend CRUD/query code.
346
+ 5. Validate build.
347
+
348
+ Never update frontend entity types from the draft patch alone when a synced manifest exists.
349
+
350
+ ## Common Agent Mistakes
351
+
352
+ | Mistake | Correct behavior |
353
+ |---|---|
354
+ | Hand-writing `.howone/database/manifest.json` | Sync from backend version. |
355
+ | Applying one operation after previewing a different patch | Apply the exact previewed patch. |
356
+ | Adding required field without default | Treat as risk; ask migration/default policy. |
357
+ | Public read list without `allowedFilters` / `allowedSorts` | Add public guardrails. |
358
+ | Using `visibility: "public"` as permission model | Write explicit `access.public`. |
359
+ | Deleting data because schema changed | Schema changes do not imply data deletion. |
360
+ | Updating `src/lib/sdk.ts` before manifest sync | Wait for synced manifest. |
361
+
362
+ ## Minimal Safe Patch Template
363
+
364
+ ```ts
365
+ const state = await client.schema.getState()
366
+
367
+ const patch = {
368
+ operations: [
369
+ {
370
+ type: 'add_field',
371
+ entityName: 'Todo',
372
+ payload: {
373
+ fieldName: 'priority',
374
+ field: {
375
+ type: 'string',
376
+ enum: ['low', 'medium', 'high'],
377
+ default: 'medium',
378
+ },
379
+ required: false,
380
+ },
381
+ },
382
+ ],
383
+ }
384
+
385
+ const preview = await client.schema.previewPatch(patch, {
386
+ expectedVersionId: state.currentVersionId,
387
+ reason: 'Add todo priority',
388
+ })
389
+
390
+ if (preview.risk?.level === 'dangerous') {
391
+ throw new Error('User confirmation required before applying schema patch')
392
+ }
393
+
394
+ const applied = await client.schema.applyPatch(patch, {
395
+ expectedVersionId: state.currentVersionId,
396
+ reason: 'Add todo priority',
397
+ })
398
+ ```
@@ -0,0 +1,309 @@
1
+ # Data Access Patterns
2
+
3
+ Use this reference to connect backend `access` design to frontend SDK calls. It answers:
4
+ **which namespace should the app call, which filters are legal, and what must not be persisted?**
5
+
6
+ For schema design, read `01-schema-design.md`. For query syntax details, read
7
+ `04-query-dsl-and-responses.md`.
8
+
9
+ ## Namespace Decision
10
+
11
+ | Schema / page need | SDK namespace | Auth header | Typical method |
12
+ |---|---|---|---|
13
+ | Current user's private records | `howone.entities.Entity` | yes | `query.mine`, `create`, `update`, `delete` |
14
+ | Logged-in shared records | `howone.entities.Entity` | yes | `query`, `get`, CRUD |
15
+ | Public list page | `howone.public.entities.Entity` | no | `query` |
16
+ | Public scoped share/detail | `howone.public.entities.Entity` | no | `queryScoped`, `get` with scope options |
17
+ | Schema tooling | `howone.schema` | yes | `previewPatch`, `applyPatch` |
18
+ | Low-level fallback | `howone.raw` / `howone.public.raw` | depends | only when typed method missing |
19
+
20
+ Do not mix authenticated and public namespaces for the same page without a clear reason.
21
+
22
+ ## Pattern A: Private Per-User Data
23
+
24
+ Use for todos, notes, journals, saved generations, personal dashboards, private settings.
25
+
26
+ Schema:
27
+
28
+ ```json
29
+ {
30
+ "visibility": "private",
31
+ "access": {
32
+ "authenticated": { "read": "own", "create": "own", "update": "own", "delete": "own" },
33
+ "public": { "read": "none", "create": "none", "update": "none", "delete": "none" }
34
+ }
35
+ }
36
+ ```
37
+
38
+ Frontend:
39
+
40
+ ```ts
41
+ const list = await howone.entities.Todo.query.mine({
42
+ page: { number: 1, size: 50 },
43
+ orderBy: { updatedDate: 'desc' },
44
+ })
45
+
46
+ await howone.entities.Todo.create({
47
+ text,
48
+ completed: false,
49
+ })
50
+ ```
51
+
52
+ Rules:
53
+
54
+ - Do not pass `ownerId`, `created_by_id`, `createdById`, `created_by_user_id`, or `puid`.
55
+ - Backend derives owner from JWT/session.
56
+ - Use `query.mine()` for owned lists.
57
+ - For first auth load, call `await howone.me()` or `await howone.requireMe()`.
58
+
59
+ ## Pattern B: Authenticated Shared Data
60
+
61
+ Use when logged-in app users can see shared records: team projects, CMS admin, internal catalogs.
62
+
63
+ Schema:
64
+
65
+ ```json
66
+ {
67
+ "visibility": "private",
68
+ "access": {
69
+ "authenticated": { "read": "all", "create": "all", "update": "all", "delete": "all" },
70
+ "public": { "read": "none", "create": "none", "update": "none", "delete": "none" }
71
+ }
72
+ }
73
+ ```
74
+
75
+ Frontend:
76
+
77
+ ```ts
78
+ const list = await howone.entities.Project.query({
79
+ page: { number: 1, size: 20 },
80
+ orderBy: { updatedDate: 'desc' },
81
+ })
82
+ ```
83
+
84
+ Rules:
85
+
86
+ - Use `query()`, not `query.mine()`.
87
+ - Still require auth.
88
+ - Be conservative with `update: "all"` and `delete: "all"` unless the app has a real role model.
89
+
90
+ ## Pattern C: Public Read-Only Content
91
+
92
+ Use for public articles, templates, products, profiles, published galleries.
93
+
94
+ Schema:
95
+
96
+ ```json
97
+ {
98
+ "visibility": "public",
99
+ "access": {
100
+ "authenticated": { "read": "all", "create": "all", "update": "all", "delete": "all" },
101
+ "public": {
102
+ "read": "list",
103
+ "create": "none",
104
+ "update": "none",
105
+ "delete": "none",
106
+ "allowedFilters": ["slug", "status", "category"],
107
+ "allowedSorts": ["publishedAt", "updatedDate"],
108
+ "defaultLimit": 20,
109
+ "maxLimit": 100
110
+ }
111
+ }
112
+ }
113
+ ```
114
+
115
+ Frontend:
116
+
117
+ ```ts
118
+ const list = await howone.public.entities.Article.query({
119
+ where: { status: 'published', category },
120
+ page: { number: 1, size: 20 },
121
+ orderBy: { publishedAt: 'desc' },
122
+ })
123
+ ```
124
+
125
+ Rules:
126
+
127
+ - Public filters must be in `allowedFilters`.
128
+ - Public sorts must be in `allowedSorts`.
129
+ - Never pass tokens or use authenticated namespace for anonymous landing pages.
130
+ - Keep public result fields safe for anonymous users.
131
+
132
+ ## Pattern D: Public Scoped Share Pages
133
+
134
+ Use for public URLs exposing one scoped record: QR profile, public report, resume, invite page.
135
+
136
+ Schema:
137
+
138
+ ```json
139
+ {
140
+ "visibility": "public",
141
+ "access": {
142
+ "authenticated": { "read": "own", "create": "own", "update": "own", "delete": "own" },
143
+ "public": {
144
+ "read": "scoped",
145
+ "create": "none",
146
+ "update": "none",
147
+ "delete": "none",
148
+ "requiredScopes": ["ownerId", "slug"],
149
+ "allowedFilters": ["slug", "active"],
150
+ "allowedSorts": ["updatedDate"],
151
+ "defaultLimit": 1,
152
+ "maxLimit": 10
153
+ }
154
+ }
155
+ }
156
+ ```
157
+
158
+ Frontend:
159
+
160
+ ```ts
161
+ const result = await howone.public.entities.QrProfile.queryScoped({
162
+ where: { ownerId, slug, active: true },
163
+ page: { number: 1, size: 1 },
164
+ })
165
+ const profile = result.items[0] ?? null
166
+ ```
167
+
168
+ Rules:
169
+
170
+ - Pass every `requiredScopes` field.
171
+ - Do not use current JWT `puid` as `ownerId` unless schema explicitly stores that as public scope.
172
+ - Do not turn scoped pages into broad list pages.
173
+ - Keep `maxLimit` small.
174
+
175
+ ## Pattern E: Public Create / Anonymous Submission
176
+
177
+ Use only for forms that must accept anonymous/public submissions: waitlist, contact, feedback,
178
+ public RSVP.
179
+
180
+ Schema:
181
+
182
+ ```json
183
+ {
184
+ "visibility": "public",
185
+ "access": {
186
+ "authenticated": { "read": "all", "create": "all", "update": "all", "delete": "all" },
187
+ "public": {
188
+ "read": "none",
189
+ "create": "scoped",
190
+ "update": "none",
191
+ "delete": "none",
192
+ "requiredScopes": ["created_by_user_id"],
193
+ "allowedFilters": [],
194
+ "allowedSorts": [],
195
+ "defaultLimit": 1,
196
+ "maxLimit": 1
197
+ }
198
+ }
199
+ }
200
+ ```
201
+
202
+ Frontend:
203
+
204
+ ```ts
205
+ await howone.public.entities.Feedback.create({
206
+ created_by_user_id: projectUserId,
207
+ message,
208
+ rating,
209
+ })
210
+ ```
211
+
212
+ Rules:
213
+
214
+ - Public create needs a clear `created_by_user_id` source when backend requires ownership mapping.
215
+ - Do not expose public read unless needed.
216
+ - Add anti-abuse UX/server constraints outside the dynamic schema when needed.
217
+ - Never persist UI-only fields from form components.
218
+
219
+ ## Pattern F: AI Workflow Output Persistence
220
+
221
+ Use for generation/analyze/report products that need history and refresh resilience.
222
+
223
+ Recommended flow:
224
+
225
+ ```ts
226
+ const pending = await howone.entities.Generation.create({
227
+ prompt,
228
+ status: 'pending',
229
+ })
230
+
231
+ try {
232
+ const output = await howone.ai.generateImage.run({ prompt })
233
+ await howone.entities.Generation.update(pending.id, {
234
+ status: 'completed',
235
+ resultUrl: output.imageUrl,
236
+ completedAt: new Date().toISOString(),
237
+ })
238
+ } catch (error) {
239
+ await howone.entities.Generation.update(pending.id, {
240
+ status: 'failed',
241
+ errorMessage: error instanceof Error ? error.message : 'Generation failed',
242
+ })
243
+ }
244
+
245
+ const history = await howone.entities.Generation.query.mine({
246
+ orderBy: { updatedDate: 'desc' },
247
+ page: { number: 1, size: 20 },
248
+ })
249
+ ```
250
+
251
+ Rules:
252
+
253
+ - Persist only fields declared in the entity schema.
254
+ - Do not persist raw workflow event streams unless schema defines an object/array field for them.
255
+ - Failure branch must persist failure if the product shows history.
256
+ - Latest result, history list, and detail pages should reload from data API, not only local state.
257
+
258
+ ## Payload Whitelist Rule
259
+
260
+ Before every create/update, mentally compute:
261
+
262
+ ```text
263
+ payload keys ⊆ entity.properties keys
264
+ ```
265
+
266
+ Allowed:
267
+
268
+ ```ts
269
+ await howone.entities.Todo.create({
270
+ text,
271
+ completed: false,
272
+ })
273
+ ```
274
+
275
+ Forbidden:
276
+
277
+ ```ts
278
+ await howone.entities.Todo.create({
279
+ text,
280
+ completed: false,
281
+ gradient_direction: 'to right', // UI-only
282
+ created_by_id: user.id, // system/owner field
283
+ workflowRawResult: output, // undeclared workflow envelope
284
+ })
285
+ ```
286
+
287
+ If the app truly needs a new persisted field, update schema first.
288
+
289
+ ## Access-to-SDK Mapping
290
+
291
+ | Access posture | Read | Create | Update/Delete |
292
+ |---|---|---|---|
293
+ | authenticated `own` | `entities.X.query.mine()` | `entities.X.create()` | `entities.X.update/delete()` |
294
+ | authenticated `all` | `entities.X.query()` | `entities.X.create()` | `entities.X.update/delete()` |
295
+ | public `list` | `public.entities.X.query()` | no | no |
296
+ | public `scoped` | `public.entities.X.queryScoped()` | only if `create: scoped/any` | only if `update: scoped/any` |
297
+ | public `none` | no public call | no public call | no public call |
298
+
299
+ ## Common Mistakes
300
+
301
+ | Mistake | Fix |
302
+ |---|---|
303
+ | Passing `created_by_user_id` for normal private data | Omit owner fields; backend derives owner. |
304
+ | Using `entities.*` on public page | Use `public.entities.*`. |
305
+ | Public filter not in `allowedFilters` | Add filter to schema or remove query. |
306
+ | Public sort not in `allowedSorts` | Add sort to schema or change UI. |
307
+ | Saving workflow output object directly | Map only declared fields. |
308
+ | Rendering history only from local state | Reload via `query.mine()` / public query. |
309
+ | Treating `visibility: "public"` as enough | Always define `access.public`. |