howone 0.1.30 → 0.1.32
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.
- package/package.json +1 -1
- package/templates/vite/.howone/skills/howone/01-architect/01-app-generation.md +138 -176
- package/templates/vite/.howone/skills/howone/01-architect/02-manifest-codegen.md +2 -2
- package/templates/vite/.howone/skills/howone/{02-database → 02-entity-schema}/01-schema-design.md +12 -30
- package/templates/vite/.howone/skills/howone/02-entity-schema/02-schema-operations.md +329 -0
- package/templates/vite/.howone/skills/howone/02-entity-schema/03-access-models.md +151 -0
- package/templates/vite/.howone/skills/howone/02-entity-schema/04-query-contracts.md +123 -0
- package/templates/vite/.howone/skills/howone/02-entity-schema/05-ai-persistence-patterns.md +255 -0
- package/templates/vite/.howone/skills/howone/{04-ai → 03-ai-capabilities}/01-ai-capability-architecture.md +42 -36
- package/templates/vite/.howone/skills/howone/{04-ai → 03-ai-capabilities}/02-workflow-contract-rules.md +5 -4
- package/templates/vite/.howone/skills/howone/{04-ai/04-service-capability-catalog.md → 03-ai-capabilities/03-service-capability-catalog.md} +15 -11
- package/templates/vite/.howone/skills/howone/03-ai-capabilities/04-workflow-operations.md +141 -0
- package/templates/vite/.howone/skills/howone/{04-ai/06-ai-feature-playbooks.md → 03-ai-capabilities/05-ai-feature-playbooks.md} +8 -29
- package/templates/vite/.howone/skills/howone/{03-sdk → 04-app-sdk}/01-client-setup.md +7 -6
- package/templates/vite/.howone/skills/howone/{03-sdk → 04-app-sdk}/07-ai-action-calls.md +5 -5
- package/templates/vite/.howone/skills/howone/{04-ai/03-ai-sdk-handoff.md → 04-app-sdk/08-ai-manifest-handoff.md} +8 -7
- package/templates/vite/.howone/skills/howone/{03-sdk/08-extension-boundaries.md → 04-app-sdk/09-extension-boundaries.md} +1 -1
- package/templates/vite/.howone/skills/howone/{02-database/03-data-access-patterns.md → 04-app-sdk/11-entity-data-access-patterns.md} +4 -4
- package/templates/vite/.howone/skills/howone/{02-database/04-query-dsl-and-responses.md → 04-app-sdk/12-query-dsl-and-responses.md} +1 -1
- package/templates/vite/.howone/skills/howone/SKILL.md +137 -133
- package/templates/vite/.howone/skills/howone/agents/openai.yaml +3 -3
- package/templates/vite/AGENTS.md +2 -2
- package/templates/vite/.howone/skills/howone/02-database/02-schema-operations.md +0 -398
- package/templates/vite/.howone/skills/howone/02-database/05-ai-persistence-patterns.md +0 -372
- package/templates/vite/.howone/skills/howone/04-ai/05-workflow-operations.md +0 -256
- /package/templates/vite/.howone/skills/howone/{03-sdk → 04-app-sdk}/02-entity-operations.md +0 -0
- /package/templates/vite/.howone/skills/howone/{03-sdk → 04-app-sdk}/03-auth.md +0 -0
- /package/templates/vite/.howone/skills/howone/{03-sdk → 04-app-sdk}/04-react-integration.md +0 -0
- /package/templates/vite/.howone/skills/howone/{03-sdk → 04-app-sdk}/05-file-upload.md +0 -0
- /package/templates/vite/.howone/skills/howone/{03-sdk → 04-app-sdk}/06-raw-http.md +0 -0
- /package/templates/vite/.howone/skills/howone/{03-sdk/09-workflow-execute-sse.md → 04-app-sdk/10-workflow-execute-sse.md} +0 -0
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
# Schema Operations
|
|
2
|
+
|
|
3
|
+
Use this reference when calling `backend-api-design` to change HowOne backend entity contracts.
|
|
4
|
+
This is a backend design reference, not an app SDK guide.
|
|
5
|
+
|
|
6
|
+
## Source Of Truth
|
|
7
|
+
|
|
8
|
+
```text
|
|
9
|
+
current backend schema = inspect result
|
|
10
|
+
backend-api-design apply result = validated contract version
|
|
11
|
+
sync_schema_artifacts output = local .howone/database manifest
|
|
12
|
+
src/lib/sdk.ts = later SDK binding, handled by SDK track
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Do not handwrite `.howone/database/*`. Sync from a backend version after apply.
|
|
16
|
+
|
|
17
|
+
## Normal Flow
|
|
18
|
+
|
|
19
|
+
For generated app work, avoid fake dry-runs. Build one correct patch and apply it.
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
1. backend-api-design { type: "get_current_schema" }
|
|
23
|
+
2. Design one complete patch.operations[] for the feature.
|
|
24
|
+
3. backend-api-design { type: "apply_schema_patch", expectedVersionId, reason, patch }
|
|
25
|
+
4. sync_schema_artifacts with the returned versionId/currentVersionId.
|
|
26
|
+
5. Read .howone/database/manifest.json.
|
|
27
|
+
6. Stop backend design; SDK/UI work is a separate track.
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
There is no schema dry-run step. If a change is destructive, narrowing, or broadens public access,
|
|
31
|
+
stop and align with the user before applying the final patch.
|
|
32
|
+
|
|
33
|
+
## Patch Shape
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"type": "apply_schema_patch",
|
|
38
|
+
"expectedVersionId": "current-version-id-from-inspect",
|
|
39
|
+
"reason": "Add personal todo storage",
|
|
40
|
+
"patch": {
|
|
41
|
+
"operations": [
|
|
42
|
+
{
|
|
43
|
+
"type": "create_entity",
|
|
44
|
+
"entityName": "Todo",
|
|
45
|
+
"payload": {
|
|
46
|
+
"description": "Personal task item.",
|
|
47
|
+
"visibility": "private",
|
|
48
|
+
"type": "object",
|
|
49
|
+
"properties": {
|
|
50
|
+
"text": { "type": "string", "description": "Task text." },
|
|
51
|
+
"completed": { "type": "boolean", "default": false }
|
|
52
|
+
},
|
|
53
|
+
"required": ["text"],
|
|
54
|
+
"access": {
|
|
55
|
+
"authenticated": { "read": "own", "create": "own", "update": "own", "delete": "own" },
|
|
56
|
+
"public": { "read": "none", "create": "none", "update": "none" }
|
|
57
|
+
},
|
|
58
|
+
"indexes": [
|
|
59
|
+
{
|
|
60
|
+
"name": "completed_updated",
|
|
61
|
+
"scope": "owner",
|
|
62
|
+
"fields": ["completed", "updatedDate"],
|
|
63
|
+
"order": { "updatedDate": "desc" }
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Critical shape rules:
|
|
74
|
+
|
|
75
|
+
- `create_entity.payload.properties` is the field map. Do not use `payload.definition`.
|
|
76
|
+
- `create_entity.payload.required` is the required field array. Do not use `requiredFields`.
|
|
77
|
+
- `update_entity.payload` is the patch itself. Do not wrap it in `payload.patch`.
|
|
78
|
+
- Entity and field names must match `^[a-zA-Z_][a-zA-Z0-9_]*$`.
|
|
79
|
+
- Backend-generated version IDs never go inside operation payloads.
|
|
80
|
+
|
|
81
|
+
## Operation Types
|
|
82
|
+
|
|
83
|
+
Patch operations accepted by `backend-api-design`:
|
|
84
|
+
|
|
85
|
+
```text
|
|
86
|
+
create_entity
|
|
87
|
+
update_entity
|
|
88
|
+
delete_entity
|
|
89
|
+
add_field
|
|
90
|
+
update_field
|
|
91
|
+
delete_field
|
|
92
|
+
set_field_required
|
|
93
|
+
unset_field_required
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Inspect/version operations are top-level tool calls, not patch operations:
|
|
97
|
+
|
|
98
|
+
```text
|
|
99
|
+
get_current_schema
|
|
100
|
+
list_entities
|
|
101
|
+
get_entity
|
|
102
|
+
list_schema_versions
|
|
103
|
+
get_schema_version
|
|
104
|
+
restore_schema_version
|
|
105
|
+
diff_schema_versions
|
|
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": "Article",
|
|
118
|
+
"payload": {
|
|
119
|
+
"description": "Published article.",
|
|
120
|
+
"visibility": "public",
|
|
121
|
+
"type": "object",
|
|
122
|
+
"properties": {
|
|
123
|
+
"title": { "type": "string" },
|
|
124
|
+
"slug": { "type": "string" },
|
|
125
|
+
"status": { "type": "string", "enum": ["draft", "published"], "default": "draft" },
|
|
126
|
+
"publishedAt": { "type": ["date", "null"], "default": null }
|
|
127
|
+
},
|
|
128
|
+
"required": ["title", "slug"],
|
|
129
|
+
"access": {
|
|
130
|
+
"authenticated": { "read": "all", "create": "all", "update": "all", "delete": "all" },
|
|
131
|
+
"public": {
|
|
132
|
+
"read": "list",
|
|
133
|
+
"create": "none",
|
|
134
|
+
"update": "none",
|
|
135
|
+
"allowedFilters": ["slug", "status"],
|
|
136
|
+
"allowedSorts": ["publishedAt", "updatedDate"],
|
|
137
|
+
"defaultLimit": 20,
|
|
138
|
+
"maxLimit": 100
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Rules:
|
|
146
|
+
|
|
147
|
+
- Include explicit `access.authenticated` and `access.public`.
|
|
148
|
+
- Required fields must exist in `properties`.
|
|
149
|
+
- Required fields with `default` or `autoGenerate` may be omitted by create callers.
|
|
150
|
+
- Do not include system fields such as `id`, `created_date`, `updated_date`, or owner IDs.
|
|
151
|
+
|
|
152
|
+
### update_entity
|
|
153
|
+
|
|
154
|
+
Use for entity metadata and contract sections.
|
|
155
|
+
|
|
156
|
+
```json
|
|
157
|
+
{
|
|
158
|
+
"type": "update_entity",
|
|
159
|
+
"entityName": "Article",
|
|
160
|
+
"payload": {
|
|
161
|
+
"description": "Published article with scoped public lookup.",
|
|
162
|
+
"access": {
|
|
163
|
+
"authenticated": { "read": "all", "create": "all", "update": "all", "delete": "all" },
|
|
164
|
+
"public": {
|
|
165
|
+
"read": "scoped",
|
|
166
|
+
"create": "none",
|
|
167
|
+
"update": "none",
|
|
168
|
+
"requiredScopes": ["slug"],
|
|
169
|
+
"allowedFilters": ["slug", "status"],
|
|
170
|
+
"allowedSorts": ["publishedAt"],
|
|
171
|
+
"defaultLimit": 1,
|
|
172
|
+
"maxLimit": 10
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
"presentation": {
|
|
176
|
+
"titleField": "title",
|
|
177
|
+
"listFields": ["title", "status", "publishedAt"]
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Allowed sections include `description`, `visibility`, `isActive`, `access`, `indexes`,
|
|
184
|
+
`relations`, `presentation`, `lifecycle`, and `performance`.
|
|
185
|
+
|
|
186
|
+
### add_field
|
|
187
|
+
|
|
188
|
+
```json
|
|
189
|
+
{
|
|
190
|
+
"type": "add_field",
|
|
191
|
+
"entityName": "Todo",
|
|
192
|
+
"payload": {
|
|
193
|
+
"fieldName": "priority",
|
|
194
|
+
"field": {
|
|
195
|
+
"type": "string",
|
|
196
|
+
"enum": ["low", "medium", "high"],
|
|
197
|
+
"default": "medium"
|
|
198
|
+
},
|
|
199
|
+
"required": false
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Rules:
|
|
205
|
+
|
|
206
|
+
- Add optional fields or fields with defaults for existing entities.
|
|
207
|
+
- If an added field must be required and has no default, align with the user first.
|
|
208
|
+
- Add indexes only for actual query paths.
|
|
209
|
+
|
|
210
|
+
### update_field
|
|
211
|
+
|
|
212
|
+
```json
|
|
213
|
+
{
|
|
214
|
+
"type": "update_field",
|
|
215
|
+
"entityName": "Todo",
|
|
216
|
+
"payload": {
|
|
217
|
+
"fieldName": "priority",
|
|
218
|
+
"patch": {
|
|
219
|
+
"enum": ["low", "medium", "high", "urgent"]
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Usually safe: adding enum values, adding descriptions, adding validation hints.
|
|
226
|
+
|
|
227
|
+
Risky: removing enum values, changing type, removing defaults, narrowing nullability, or making
|
|
228
|
+
existing data invalid.
|
|
229
|
+
|
|
230
|
+
### delete_field
|
|
231
|
+
|
|
232
|
+
```json
|
|
233
|
+
{
|
|
234
|
+
"type": "delete_field",
|
|
235
|
+
"entityName": "Todo",
|
|
236
|
+
"payload": {
|
|
237
|
+
"fieldName": "legacyTag",
|
|
238
|
+
"removeFromData": false
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Default `removeFromData` to `false`. `true` requires explicit user confirmation.
|
|
244
|
+
|
|
245
|
+
### set_field_required / unset_field_required
|
|
246
|
+
|
|
247
|
+
```json
|
|
248
|
+
{
|
|
249
|
+
"type": "set_field_required",
|
|
250
|
+
"entityName": "Todo",
|
|
251
|
+
"payload": { "fieldName": "text" }
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Setting required on existing data is risky unless a default exists or old records are known valid.
|
|
256
|
+
Unsetting required is usually safe.
|
|
257
|
+
|
|
258
|
+
### delete_entity
|
|
259
|
+
|
|
260
|
+
```json
|
|
261
|
+
{
|
|
262
|
+
"type": "delete_entity",
|
|
263
|
+
"entityName": "Todo",
|
|
264
|
+
"payload": {
|
|
265
|
+
"mode": "soft",
|
|
266
|
+
"deleteData": false
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Rules:
|
|
272
|
+
|
|
273
|
+
- Default to `mode: "soft"`.
|
|
274
|
+
- `mode: "hard"` requires explicit user request.
|
|
275
|
+
- `deleteData: true` requires explicit confirmation.
|
|
276
|
+
|
|
277
|
+
## Risk Checklist
|
|
278
|
+
|
|
279
|
+
Stop before apply and align with the user when the patch:
|
|
280
|
+
|
|
281
|
+
- deletes entity definitions;
|
|
282
|
+
- deletes fields used by app UI or AI outputs;
|
|
283
|
+
- removes data from historical records;
|
|
284
|
+
- changes field types;
|
|
285
|
+
- makes fields required without defaults;
|
|
286
|
+
- broadens public read;
|
|
287
|
+
- enables public create/update;
|
|
288
|
+
- removes public scopes/filter/sort limits.
|
|
289
|
+
|
|
290
|
+
Usually safe to apply directly:
|
|
291
|
+
|
|
292
|
+
- create a new entity with explicit private/public access;
|
|
293
|
+
- add optional fields;
|
|
294
|
+
- add fields with defaults;
|
|
295
|
+
- add enum values;
|
|
296
|
+
- add indexes for known query paths;
|
|
297
|
+
- add presentation/performance metadata.
|
|
298
|
+
|
|
299
|
+
## Sync Handoff
|
|
300
|
+
|
|
301
|
+
After apply, use the returned version hint:
|
|
302
|
+
|
|
303
|
+
```json
|
|
304
|
+
{
|
|
305
|
+
"next": {
|
|
306
|
+
"recommendedAction": "sync_schema_artifacts",
|
|
307
|
+
"versionId": "dbv_next"
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
Then:
|
|
313
|
+
|
|
314
|
+
1. Call `sync_schema_artifacts` with the version ID and app root.
|
|
315
|
+
2. Read `.howone/database/manifest.json`.
|
|
316
|
+
3. Leave backend track.
|
|
317
|
+
4. If app code must call the entity, read SDK track files and update `src/lib/sdk.ts`.
|
|
318
|
+
|
|
319
|
+
## Common Mistakes
|
|
320
|
+
|
|
321
|
+
| Mistake | Correct behavior |
|
|
322
|
+
|---|---|
|
|
323
|
+
| `payload.definition` for create | Put `properties`, `required`, `access`, etc. directly under `payload`. |
|
|
324
|
+
| `payload.patch` for update | Put changed contract sections directly under `payload`. |
|
|
325
|
+
| `fields[]` or `requiredFields` | Use `properties` object map and `required` array. |
|
|
326
|
+
| Handwriting `.howone/database/manifest.json` | Use `sync_schema_artifacts`. |
|
|
327
|
+
| Updating SDK bindings from draft patch | Read synced manifest first. |
|
|
328
|
+
| Using public visibility as permission model | Write explicit `access.public`. |
|
|
329
|
+
| Deleting data because schema changed | Schema changes do not imply data deletion. |
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Backend Access Models
|
|
2
|
+
|
|
3
|
+
Use this reference while designing entity `access` contracts with `backend-api-design`.
|
|
4
|
+
It is backend-only: do not write SDK calls from this file.
|
|
5
|
+
|
|
6
|
+
Access answers four questions:
|
|
7
|
+
|
|
8
|
+
1. Who can read records?
|
|
9
|
+
2. Who can create records?
|
|
10
|
+
3. Who can update/delete records?
|
|
11
|
+
4. Which anonymous public operations are safe?
|
|
12
|
+
|
|
13
|
+
## Authenticated Access
|
|
14
|
+
|
|
15
|
+
Authenticated access is for logged-in users. Values are `own`, `all`, or `none`.
|
|
16
|
+
|
|
17
|
+
Private per-user records:
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"access": {
|
|
22
|
+
"authenticated": {
|
|
23
|
+
"read": "own",
|
|
24
|
+
"create": "own",
|
|
25
|
+
"update": "own",
|
|
26
|
+
"delete": "own"
|
|
27
|
+
},
|
|
28
|
+
"public": {
|
|
29
|
+
"read": "none",
|
|
30
|
+
"create": "none",
|
|
31
|
+
"update": "none",
|
|
32
|
+
"delete": "none"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Shared authenticated records:
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"access": {
|
|
43
|
+
"authenticated": {
|
|
44
|
+
"read": "all",
|
|
45
|
+
"create": "all",
|
|
46
|
+
"update": "all",
|
|
47
|
+
"delete": "all"
|
|
48
|
+
},
|
|
49
|
+
"public": {
|
|
50
|
+
"read": "none",
|
|
51
|
+
"create": "none",
|
|
52
|
+
"update": "none",
|
|
53
|
+
"delete": "none"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Use `all` conservatively. If the product implies team roles, moderation, or ownership transitions
|
|
60
|
+
that the dynamic schema cannot express yet, keep the schema narrow and leave role logic to a future
|
|
61
|
+
platform capability or app-owned guard.
|
|
62
|
+
|
|
63
|
+
## Public Read
|
|
64
|
+
|
|
65
|
+
Public read is for anonymous access. Values are `none`, `list`, or `scoped`.
|
|
66
|
+
|
|
67
|
+
Use `list` only when broad anonymous browsing is intended:
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"access": {
|
|
72
|
+
"authenticated": {
|
|
73
|
+
"read": "all",
|
|
74
|
+
"create": "all",
|
|
75
|
+
"update": "all",
|
|
76
|
+
"delete": "all"
|
|
77
|
+
},
|
|
78
|
+
"public": {
|
|
79
|
+
"read": "list",
|
|
80
|
+
"create": "none",
|
|
81
|
+
"update": "none",
|
|
82
|
+
"delete": "none",
|
|
83
|
+
"allowedFilters": ["status", "category", "slug"],
|
|
84
|
+
"allowedSorts": ["publishedAt", "updatedDate"],
|
|
85
|
+
"defaultLimit": 20,
|
|
86
|
+
"maxLimit": 100
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Use `scoped` when a public page should expose only records that match route-bound scope fields:
|
|
93
|
+
|
|
94
|
+
```json
|
|
95
|
+
{
|
|
96
|
+
"access": {
|
|
97
|
+
"public": {
|
|
98
|
+
"read": "scoped",
|
|
99
|
+
"requiredScopes": ["shareId"],
|
|
100
|
+
"allowedFilters": ["shareId", "active"],
|
|
101
|
+
"allowedSorts": ["updatedDate"],
|
|
102
|
+
"defaultLimit": 1,
|
|
103
|
+
"maxLimit": 1
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Public rules:
|
|
110
|
+
|
|
111
|
+
- Public filters must be listed in `allowedFilters`.
|
|
112
|
+
- Public sorts must be listed in `allowedSorts`.
|
|
113
|
+
- Scoped reads must include every `requiredScopes` field.
|
|
114
|
+
- Public records must not contain private prompts, tokens, internal review states, or owner-only data.
|
|
115
|
+
- Public list entities need indexes covering common filters and sorts.
|
|
116
|
+
|
|
117
|
+
## Public Create
|
|
118
|
+
|
|
119
|
+
Public create is only for anonymous submissions such as waitlists, contact forms, feedback, and
|
|
120
|
+
public RSVP flows.
|
|
121
|
+
|
|
122
|
+
```json
|
|
123
|
+
{
|
|
124
|
+
"access": {
|
|
125
|
+
"public": {
|
|
126
|
+
"read": "none",
|
|
127
|
+
"create": "scoped",
|
|
128
|
+
"update": "none",
|
|
129
|
+
"delete": "none",
|
|
130
|
+
"requiredScopes": ["created_by_user_id"],
|
|
131
|
+
"allowedFilters": [],
|
|
132
|
+
"allowedSorts": [],
|
|
133
|
+
"defaultLimit": 1,
|
|
134
|
+
"maxLimit": 1
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Do not enable public create for user libraries, generated histories, admin content, or entities that
|
|
141
|
+
store sensitive workflow output. Add app-owned anti-abuse controls when anonymous submission is open.
|
|
142
|
+
|
|
143
|
+
## Access Design Checklist
|
|
144
|
+
|
|
145
|
+
- The entity has the narrowest access model that supports the product.
|
|
146
|
+
- Private history uses authenticated `own`.
|
|
147
|
+
- Shared admin/internal data uses authenticated `all` only when acceptable.
|
|
148
|
+
- Public list data exposes only safe fields.
|
|
149
|
+
- Public scoped pages use stable scope fields and low `maxLimit`.
|
|
150
|
+
- Public create has a clear ownership/scope story and no anonymous read by default.
|
|
151
|
+
- Access decisions are synced before SDK/UI implementation starts.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Backend Query Contracts
|
|
2
|
+
|
|
3
|
+
Use this reference while designing entity query behavior: filters, sorts, pagination, indexes, and
|
|
4
|
+
public constraints. It is backend-only; SDK query syntax belongs in `04-app-sdk/12-query-dsl-and-responses.md`.
|
|
5
|
+
|
|
6
|
+
## Query Surface
|
|
7
|
+
|
|
8
|
+
Every list/detail experience should be traceable to fields declared in the entity schema:
|
|
9
|
+
|
|
10
|
+
| Need | Contract field |
|
|
11
|
+
|---|---|
|
|
12
|
+
| Filter by status/category/slug | `access.public.allowedFilters` for public reads; schema field exists |
|
|
13
|
+
| Sort by date/rank/title | `access.public.allowedSorts` for public reads; `performance.allowedSorts` for app reads |
|
|
14
|
+
| Fast owner history | owner-scoped index such as `["status", "updatedDate"]` |
|
|
15
|
+
| Public one-record page | `public.read = "scoped"`, `requiredScopes`, low `maxLimit` |
|
|
16
|
+
| Search | explicit product support; do not use as a replacement for scopes |
|
|
17
|
+
|
|
18
|
+
## Filter Fields
|
|
19
|
+
|
|
20
|
+
Only expose fields that are safe and stable.
|
|
21
|
+
|
|
22
|
+
Good public filters:
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"allowedFilters": ["slug", "status", "category", "active"]
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Avoid public filters for:
|
|
31
|
+
|
|
32
|
+
- private prompt text;
|
|
33
|
+
- owner/user identifiers unless they are intentional route scopes;
|
|
34
|
+
- internal moderation fields;
|
|
35
|
+
- payment, token, or provider metadata;
|
|
36
|
+
- high-cardinality fields that have no index and will be queried frequently.
|
|
37
|
+
|
|
38
|
+
## Sort Fields
|
|
39
|
+
|
|
40
|
+
Prefer predictable date or rank fields:
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"allowedSorts": ["publishedAt", "updatedDate"],
|
|
45
|
+
"defaultLimit": 20,
|
|
46
|
+
"maxLimit": 100
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Rules:
|
|
51
|
+
|
|
52
|
+
- Public sort fields must be explicitly listed.
|
|
53
|
+
- Sort fields used by large lists should have supporting indexes.
|
|
54
|
+
- Do not expose arbitrary revenue, score, or internal ranking fields publicly unless the product
|
|
55
|
+
explicitly needs them.
|
|
56
|
+
- Keep public `maxLimit` bounded.
|
|
57
|
+
|
|
58
|
+
## Index Planning
|
|
59
|
+
|
|
60
|
+
Design indexes from actual access patterns:
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{
|
|
64
|
+
"indexes": [
|
|
65
|
+
{ "name": "owner_updated", "fields": ["updatedDate"], "scope": "owner" },
|
|
66
|
+
{ "name": "owner_status_updated", "fields": ["status", "updatedDate"], "scope": "owner" },
|
|
67
|
+
{ "name": "public_slug_unique", "fields": ["slug"], "unique": true },
|
|
68
|
+
{ "name": "public_status_published", "fields": ["status", "publishedAt"], "scope": "global" }
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Use owner-scoped indexes for private per-user histories. Use global indexes for anonymous public
|
|
74
|
+
lists. Use unique indexes for slug/share IDs when routes depend on uniqueness.
|
|
75
|
+
|
|
76
|
+
## System Fields
|
|
77
|
+
|
|
78
|
+
Generated records usually expose system fields such as:
|
|
79
|
+
|
|
80
|
+
| Concept | Typical field |
|
|
81
|
+
|---|---|
|
|
82
|
+
| record id | `id` |
|
|
83
|
+
| created date | `createdDate` |
|
|
84
|
+
| updated date | `updatedDate` |
|
|
85
|
+
| owner | `createdById` |
|
|
86
|
+
| schema version id | `schemaVersionId` |
|
|
87
|
+
| schema version number | `schemaVersionNumber` |
|
|
88
|
+
|
|
89
|
+
Do not design app behavior around raw storage-only names unless the manifest or SDK reference
|
|
90
|
+
requires that shape.
|
|
91
|
+
|
|
92
|
+
## Pagination Defaults
|
|
93
|
+
|
|
94
|
+
Every list contract should define practical limits:
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"performance": {
|
|
99
|
+
"defaultLimit": 20,
|
|
100
|
+
"maxLimit": 100,
|
|
101
|
+
"allowedSorts": ["updatedDate", "createdDate"]
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Public scoped detail pages should usually use:
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"defaultLimit": 1,
|
|
111
|
+
"maxLimit": 1
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Query Contract Checklist
|
|
116
|
+
|
|
117
|
+
- Every planned filter field exists in `properties`.
|
|
118
|
+
- Public filters and sorts are explicitly allowlisted.
|
|
119
|
+
- Owner/private histories have owner-scoped indexes.
|
|
120
|
+
- Public lists have global indexes for common filter/sort combinations.
|
|
121
|
+
- Scoped public pages include required scope fields and low limits.
|
|
122
|
+
- List pages have pagination limits before SDK/UI code starts.
|
|
123
|
+
- Query implementation is written only after `.howone/database/manifest.json` is synced.
|