howone 0.1.31 → 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 +33 -27
- package/templates/vite/.howone/skills/howone/01-architect/02-manifest-codegen.md +2 -2
- package/templates/vite/.howone/skills/howone/02-entity-schema/01-schema-design.md +9 -29
- package/templates/vite/.howone/skills/howone/02-entity-schema/02-schema-operations.md +166 -235
- 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 +84 -201
- package/templates/vite/.howone/skills/howone/03-ai-capabilities/01-ai-capability-architecture.md +37 -32
- package/templates/vite/.howone/skills/howone/03-ai-capabilities/02-workflow-contract-rules.md +5 -4
- package/templates/vite/.howone/skills/howone/03-ai-capabilities/04-workflow-operations.md +89 -204
- package/templates/vite/.howone/skills/howone/03-ai-capabilities/05-ai-feature-playbooks.md +8 -29
- package/templates/vite/.howone/skills/howone/04-app-sdk/01-client-setup.md +1 -2
- package/templates/vite/.howone/skills/howone/04-app-sdk/07-ai-action-calls.md +2 -2
- package/templates/vite/.howone/skills/howone/04-app-sdk/08-ai-manifest-handoff.md +6 -5
- package/templates/vite/.howone/skills/howone/04-app-sdk/09-extension-boundaries.md +1 -1
- package/templates/vite/.howone/skills/howone/{02-entity-schema/03-data-access-patterns.md → 04-app-sdk/11-entity-data-access-patterns.md} +4 -4
- package/templates/vite/.howone/skills/howone/{02-entity-schema/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 +112 -75
|
@@ -1,108 +1,108 @@
|
|
|
1
1
|
# Schema Operations
|
|
2
2
|
|
|
3
|
-
Use this reference when
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
For schema design decisions, read `01-schema-design.md` first.
|
|
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.
|
|
7
5
|
|
|
8
6
|
## Source Of Truth
|
|
9
7
|
|
|
10
8
|
```text
|
|
11
|
-
|
|
12
|
-
backend
|
|
13
|
-
|
|
14
|
-
src/lib/sdk.ts
|
|
15
|
-
frontend code = consumer of generated bindings
|
|
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
|
|
16
13
|
```
|
|
17
14
|
|
|
18
|
-
Do not
|
|
19
|
-
|
|
20
|
-
## Preferred Patch Flow
|
|
21
|
-
|
|
22
|
-
Use patch flow for most feature-level changes:
|
|
15
|
+
Do not handwrite `.howone/database/*`. Sync from a backend version after apply.
|
|
23
16
|
|
|
24
|
-
|
|
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.
|
|
17
|
+
## Normal Flow
|
|
34
18
|
|
|
35
|
-
|
|
19
|
+
For generated app work, avoid fake dry-runs. Build one correct patch and apply it.
|
|
36
20
|
|
|
37
|
-
|
|
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
|
+
```
|
|
38
29
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const definitions = await client.schema.listDefinitions()
|
|
42
|
-
const todo = await client.schema.getDefinition('Todo')
|
|
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.
|
|
43
32
|
|
|
44
|
-
|
|
45
|
-
expectedVersionId: state.currentVersionId,
|
|
46
|
-
reason: 'Add priority field to Todo',
|
|
47
|
-
})
|
|
33
|
+
## Patch Shape
|
|
48
34
|
|
|
49
|
-
|
|
50
|
-
|
|
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
|
+
}
|
|
51
70
|
}
|
|
52
|
-
|
|
53
|
-
const applied = await client.schema.applyPatch(patch, {
|
|
54
|
-
expectedVersionId: state.currentVersionId,
|
|
55
|
-
reason: 'Add priority field to Todo',
|
|
56
|
-
})
|
|
57
71
|
```
|
|
58
72
|
|
|
59
|
-
|
|
73
|
+
Critical shape rules:
|
|
60
74
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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. |
|
|
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.
|
|
73
80
|
|
|
74
81
|
## Operation Types
|
|
75
82
|
|
|
76
|
-
|
|
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:
|
|
83
|
+
Patch operations accepted by `backend-api-design`:
|
|
91
84
|
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
|
98
94
|
```
|
|
99
95
|
|
|
100
|
-
|
|
96
|
+
Inspect/version operations are top-level tool calls, not patch operations:
|
|
101
97
|
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
106
|
```
|
|
107
107
|
|
|
108
108
|
## Operation Payloads
|
|
@@ -114,20 +114,28 @@ Use when the entity does not exist.
|
|
|
114
114
|
```json
|
|
115
115
|
{
|
|
116
116
|
"type": "create_entity",
|
|
117
|
-
"entityName": "
|
|
117
|
+
"entityName": "Article",
|
|
118
118
|
"payload": {
|
|
119
|
-
"
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
"
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
|
131
139
|
}
|
|
132
140
|
}
|
|
133
141
|
}
|
|
@@ -136,53 +144,44 @@ Use when the entity does not exist.
|
|
|
136
144
|
|
|
137
145
|
Rules:
|
|
138
146
|
|
|
139
|
-
- `
|
|
140
|
-
-
|
|
141
|
-
-
|
|
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.
|
|
142
151
|
|
|
143
152
|
### update_entity
|
|
144
153
|
|
|
145
|
-
Use for metadata and contract sections
|
|
154
|
+
Use for entity metadata and contract sections.
|
|
146
155
|
|
|
147
156
|
```json
|
|
148
157
|
{
|
|
149
158
|
"type": "update_entity",
|
|
150
159
|
"entityName": "Article",
|
|
151
160
|
"payload": {
|
|
152
|
-
"
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
},
|
|
166
|
-
"performance": {
|
|
167
|
-
"defaultLimit": 20,
|
|
168
|
-
"maxLimit": 100,
|
|
169
|
-
"allowedSorts": ["publishedAt", "updatedDate"]
|
|
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
|
|
170
173
|
}
|
|
174
|
+
},
|
|
175
|
+
"presentation": {
|
|
176
|
+
"titleField": "title",
|
|
177
|
+
"listFields": ["title", "status", "publishedAt"]
|
|
171
178
|
}
|
|
172
179
|
}
|
|
173
180
|
}
|
|
174
181
|
```
|
|
175
182
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
- `description`
|
|
179
|
-
- `visibility`
|
|
180
|
-
- `access`
|
|
181
|
-
- `indexes`
|
|
182
|
-
- `relations`
|
|
183
|
-
- `presentation`
|
|
184
|
-
- `lifecycle`
|
|
185
|
-
- `performance`
|
|
183
|
+
Allowed sections include `description`, `visibility`, `isActive`, `access`, `indexes`,
|
|
184
|
+
`relations`, `presentation`, `lifecycle`, and `performance`.
|
|
186
185
|
|
|
187
186
|
### add_field
|
|
188
187
|
|
|
@@ -204,9 +203,9 @@ Use this for:
|
|
|
204
203
|
|
|
205
204
|
Rules:
|
|
206
205
|
|
|
207
|
-
-
|
|
208
|
-
- If
|
|
209
|
-
- Add indexes only
|
|
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.
|
|
210
209
|
|
|
211
210
|
### update_field
|
|
212
211
|
|
|
@@ -223,13 +222,10 @@ Rules:
|
|
|
223
222
|
}
|
|
224
223
|
```
|
|
225
224
|
|
|
226
|
-
|
|
225
|
+
Usually safe: adding enum values, adding descriptions, adding validation hints.
|
|
227
226
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
- Changing type: high risk.
|
|
231
|
-
- Making nullable field required: high risk.
|
|
232
|
-
- Removing default: risky for create flows.
|
|
227
|
+
Risky: removing enum values, changing type, removing defaults, narrowing nullability, or making
|
|
228
|
+
existing data invalid.
|
|
233
229
|
|
|
234
230
|
### delete_field
|
|
235
231
|
|
|
@@ -244,11 +240,7 @@ Risk levels:
|
|
|
244
240
|
}
|
|
245
241
|
```
|
|
246
242
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
- Default `removeFromData` to `false`.
|
|
250
|
-
- Only use `removeFromData: true` after explicit confirmation.
|
|
251
|
-
- Removing from schema does not necessarily remove historical data.
|
|
243
|
+
Default `removeFromData` to `false`. `true` requires explicit user confirmation.
|
|
252
244
|
|
|
253
245
|
### set_field_required / unset_field_required
|
|
254
246
|
|
|
@@ -260,11 +252,8 @@ Rules:
|
|
|
260
252
|
}
|
|
261
253
|
```
|
|
262
254
|
|
|
263
|
-
|
|
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.
|
|
255
|
+
Setting required on existing data is risky unless a default exists or old records are known valid.
|
|
256
|
+
Unsetting required is usually safe.
|
|
268
257
|
|
|
269
258
|
### delete_entity
|
|
270
259
|
|
|
@@ -273,7 +262,7 @@ Rules:
|
|
|
273
262
|
"type": "delete_entity",
|
|
274
263
|
"entityName": "Todo",
|
|
275
264
|
"payload": {
|
|
276
|
-
"
|
|
265
|
+
"mode": "soft",
|
|
277
266
|
"deleteData": false
|
|
278
267
|
}
|
|
279
268
|
}
|
|
@@ -281,52 +270,35 @@ Rules:
|
|
|
281
270
|
|
|
282
271
|
Rules:
|
|
283
272
|
|
|
284
|
-
- Default to soft
|
|
285
|
-
-
|
|
286
|
-
- `deleteData: true` requires explicit confirmation
|
|
273
|
+
- Default to `mode: "soft"`.
|
|
274
|
+
- `mode: "hard"` requires explicit user request.
|
|
275
|
+
- `deleteData: true` requires explicit confirmation.
|
|
287
276
|
|
|
288
277
|
## Risk Checklist
|
|
289
278
|
|
|
290
|
-
Stop and
|
|
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.
|
|
279
|
+
Stop before apply and align with the user when the patch:
|
|
310
280
|
|
|
311
|
-
|
|
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.
|
|
312
289
|
|
|
313
|
-
|
|
290
|
+
Usually safe to apply directly:
|
|
314
291
|
|
|
315
|
-
|
|
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.
|
|
316
298
|
|
|
317
|
-
|
|
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.
|
|
299
|
+
## Sync Handoff
|
|
326
300
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
After apply, the result should include a version hint:
|
|
301
|
+
After apply, use the returned version hint:
|
|
330
302
|
|
|
331
303
|
```json
|
|
332
304
|
{
|
|
@@ -337,62 +309,21 @@ After apply, the result should include a version hint:
|
|
|
337
309
|
}
|
|
338
310
|
```
|
|
339
311
|
|
|
340
|
-
|
|
312
|
+
Then:
|
|
341
313
|
|
|
342
|
-
1.
|
|
314
|
+
1. Call `sync_schema_artifacts` with the version ID and app root.
|
|
343
315
|
2. Read `.howone/database/manifest.json`.
|
|
344
|
-
3.
|
|
345
|
-
4.
|
|
346
|
-
5. Validate build.
|
|
347
|
-
|
|
348
|
-
Never update frontend entity types from the draft patch alone when a synced manifest exists.
|
|
316
|
+
3. Leave backend track.
|
|
317
|
+
4. If app code must call the entity, read SDK track files and update `src/lib/sdk.ts`.
|
|
349
318
|
|
|
350
|
-
## Common
|
|
319
|
+
## Common Mistakes
|
|
351
320
|
|
|
352
321
|
| Mistake | Correct behavior |
|
|
353
322
|
|---|---|
|
|
354
|
-
|
|
|
355
|
-
|
|
|
356
|
-
|
|
|
357
|
-
|
|
|
358
|
-
|
|
|
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`. |
|
|
359
329
|
| 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,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.
|