Skip to content

Commit 9d9de01

Browse files
committed
docs: sync remaining ui + protocol docs; re-audit error-handling/http-protocol (#1880 Wave 1, batch 7 — final)
Final batch — completes the 164-doc hand-written implementation-accuracy audit. 13 docs audited AND adversarially verified. Includes a clean re-audit of the two protocol docs whose first pass was interrupted by a usage limit (their partial diffs were reverted earlier): - protocol/kernel/error-handling: replaced fabricated SCREAMING_SNAKE error codes with the real lowercase StandardErrorCode values (unauthenticated, permission_denied, resource_not_found, delete_restricted, rate_limit_exceeded, service_unavailable, …) and removed the fabricated "WebSocket-Specific Errors" / "Business Logic Errors" sections — the error contract is HTTP/REST, not multi-channel. - protocol/kernel/http-protocol: corrected discovery routes, response-shape names, and fetch examples to the real /api/v1 surface. - ui/{forms,views,dashboards,pages,setup-app,...}: example, flag and field-name corrections. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012urEihGTAsQP2xizAqfCwt
1 parent 9c1c5ef commit 9d9de01

12 files changed

Lines changed: 211 additions & 331 deletions

content/docs/protocol/kernel/error-handling.mdx

Lines changed: 70 additions & 186 deletions
Large diffs are not rendered by default.

content/docs/protocol/kernel/http-protocol.mdx

Lines changed: 116 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ GET /.well-known/objectstack HTTP/1.1
2929
Host: api.acme.com
3030
```
3131

32-
`/.well-known/objectstack` redirects to the canonical discovery route:
32+
`/.well-known/objectstack` and the versioned `/api/v1/discovery` route both return the
33+
discovery document directly — there is no HTTP redirect between them:
3334
```http
3435
GET /api/v1/discovery HTTP/1.1
3536
```
@@ -49,16 +50,12 @@ GET /api/v1/discovery HTTP/1.1
4950
"storage": "/api/v1/storage",
5051
"graphql": "/api/v1/graphql"
5152
},
52-
"features": {
53-
"graphql": true,
54-
"websockets": false,
55-
"search": true,
56-
"files": true,
57-
"analytics": true,
58-
"ai": false,
59-
"workflow": true,
60-
"notifications": true,
61-
"i18n": true
53+
"services": {
54+
"data": { "enabled": true, "status": "available", "route": "/api/v1/data", "provider": "objectql" },
55+
"metadata": { "enabled": true, "status": "available", "route": "/api/v1/meta", "provider": "objectql" },
56+
"auth": { "enabled": true, "status": "available", "route": "/api/v1/auth", "provider": "plugin-auth" },
57+
"workflow": { "enabled": false, "status": "unavailable", "message": "Install plugin-workflow to enable" },
58+
"ai": { "enabled": false, "status": "unavailable", "message": "Install plugin-ai to enable" }
6259
},
6360
"locale": {
6461
"default": "en-US",
@@ -71,7 +68,7 @@ GET /api/v1/discovery HTTP/1.1
7168
**Why discovery matters:**
7269
- **Environment agnostic:** Works across dev, staging, production without hardcoding URLs
7370
- **Version tolerance:** API routes can change without breaking clients
74-
- **Feature detection:** Clients enable/disable features based on server capabilities
71+
- **Feature detection:** Clients enable/disable features by inspecting each entry's `enabled` / `status` in the `services` map
7572
- **Automatic configuration:** SDKs auto-configure from discovery response
7673

7774
## Standard Data API
@@ -147,32 +144,29 @@ Authorization: Bearer <token>
147144

148145
**Success Response:**
149146

150-
List responses are wrapped in the standard `{ success, data }` envelope. For
151-
queries, `data` is a `FindDataResponse` containing `object`, `records`, and the
152-
optional `total` / `hasMore` pagination hints.
147+
A list query returns a `FindDataResponse` directly — there is no outer envelope.
148+
The response carries the `object` name, the `records` array, and the optional
149+
`total` / `hasMore` pagination hints.
153150

154151
```json
155152
{
156-
"success": true,
157-
"data": {
158-
"object": "task",
159-
"records": [
160-
{
161-
"id": "task_456",
162-
"title": "Implement login page",
163-
"status": "in_progress",
164-
"created_at": "2024-01-15T10:30:00Z"
165-
},
166-
{
167-
"id": "task_789",
168-
"title": "Fix navigation bug",
169-
"status": "todo",
170-
"created_at": "2024-01-14T16:20:00Z"
171-
}
172-
],
173-
"total": 47,
174-
"hasMore": true
175-
}
153+
"object": "task",
154+
"records": [
155+
{
156+
"id": "task_456",
157+
"title": "Implement login page",
158+
"status": "in_progress",
159+
"created_at": "2024-01-15T10:30:00Z"
160+
},
161+
{
162+
"id": "task_789",
163+
"title": "Fix navigation bug",
164+
"status": "todo",
165+
"created_at": "2024-01-14T16:20:00Z"
166+
}
167+
],
168+
"total": 47,
169+
"hasMore": true
176170
}
177171
```
178172

@@ -286,13 +280,10 @@ record count and a `hasMore` flag:
286280

287281
```json
288282
{
289-
"success": true,
290-
"data": {
291-
"object": "account",
292-
"records": [],
293-
"total": 247,
294-
"hasMore": true
295-
}
283+
"object": "account",
284+
"records": [],
285+
"total": 247,
286+
"hasMore": true
296287
}
297288
```
298289

@@ -311,18 +302,15 @@ GET /api/data/account?select=id,name,industry,revenue
311302
**Response:**
312303
```json
313304
{
314-
"success": true,
315-
"data": {
316-
"object": "account",
317-
"records": [
318-
{
319-
"id": "acc_123",
320-
"name": "Acme Corp",
321-
"industry": "Technology",
322-
"revenue": 5000000
323-
}
324-
]
325-
}
305+
"object": "account",
306+
"records": [
307+
{
308+
"id": "acc_123",
309+
"name": "Acme Corp",
310+
"industry": "Technology",
311+
"revenue": 5000000
312+
}
313+
]
326314
}
327315
```
328316

@@ -345,28 +333,25 @@ GET /api/data/task?expand=assignee,project
345333
**Response:**
346334
```json
347335
{
348-
"success": true,
349-
"data": {
350-
"object": "task",
351-
"records": [
352-
{
353-
"id": "task_123",
354-
"title": "Implement API",
355-
"assignee_id": "user_456",
356-
"project_id": "proj_789",
357-
"assignee": {
358-
"id": "user_456",
359-
"name": "John Doe",
360-
"email": "john@acme.com"
361-
},
362-
"project": {
363-
"id": "proj_789",
364-
"name": "CRM Rebuild",
365-
"status": "active"
366-
}
336+
"object": "task",
337+
"records": [
338+
{
339+
"id": "task_123",
340+
"title": "Implement API",
341+
"assignee_id": "user_456",
342+
"project_id": "proj_789",
343+
"assignee": {
344+
"id": "user_456",
345+
"name": "John Doe",
346+
"email": "john@acme.com"
347+
},
348+
"project": {
349+
"id": "proj_789",
350+
"name": "CRM Rebuild",
351+
"status": "active"
367352
}
368-
]
369-
}
353+
}
354+
]
370355
}
371356
```
372357

@@ -394,10 +379,15 @@ Authorization: Bearer <token>
394379
```
395380

396381
**Success Response (HTTP 200):**
382+
383+
A single-record read returns a `GetDataResponse`: the `object` name, the record
384+
`id`, and the full record under `record`.
385+
397386
```json
398387
{
399-
"success": true,
400-
"data": {
388+
"object": "account",
389+
"id": "acc_123",
390+
"record": {
401391
"id": "acc_123",
402392
"name": "Acme Corporation",
403393
"industry": "Technology",
@@ -446,10 +436,15 @@ Content-Type: application/json
446436
```
447437

448438
**Success Response (HTTP 201):**
439+
440+
Create returns a `CreateDataResponse`: the `object` name, the new record `id`, and
441+
the created record (including server-generated fields) under `record`.
442+
449443
```json
450444
{
451-
"success": true,
452-
"data": {
445+
"object": "account",
446+
"id": "acc_124",
447+
"record": {
453448
"id": "acc_124",
454449
"name": "TechStart Inc",
455450
"industry": "SaaS",
@@ -505,10 +500,15 @@ Content-Type: application/json
505500
```
506501

507502
**Success Response (HTTP 200):**
503+
504+
Update returns an `UpdateDataResponse`: the `object` name, the record `id`, and the
505+
updated record under `record`.
506+
508507
```json
509508
{
510-
"success": true,
511-
"data": {
509+
"object": "account",
510+
"id": "acc_123",
511+
"record": {
512512
"id": "acc_123",
513513
"name": "Acme Corporation",
514514
"industry": "Technology",
@@ -524,20 +524,14 @@ Content-Type: application/json
524524
**Note:** Only fields included in the request body are updated. Other fields remain unchanged.
525525

526526
**Read-only fields:**
527-
Attempting to update read-only fields (e.g., `id`, `created_at`) returns HTTP 400:
527+
Caller-supplied writes to statically read-only fields (e.g., `id`, `created_at`) are
528+
**silently stripped** from a non-system update rather than rejected (#2948): the
529+
request succeeds with HTTP 200 and every other field is applied, but the read-only
530+
field is left unchanged.
528531

529-
```json
530-
{
531-
"error": "Cannot update read-only fields",
532-
"code": "VALIDATION_FAILED",
533-
"fields": [
534-
{
535-
"field": "created_at",
536-
"message": "Field is read-only"
537-
}
538-
]
539-
}
540-
```
532+
> **Note:** This differs from field-level security. A write to a field the caller
533+
> lacks edit permission on is *rejected* with `403 PermissionDeniedError`, not
534+
> stripped.
541535
542536
### Delete Record
543537

@@ -555,31 +549,25 @@ Authorization: Bearer <token>
555549
```
556550

557551
**Success Response (HTTP 200):**
558-
```json
559-
{
560-
"success": true,
561-
"data": {
562-
"id": "acc_123",
563-
"deleted": true
564-
}
565-
}
566-
```
567552

568-
**Soft Delete (if enabled):**
569-
If object has `enable.trash` enabled, record is marked deleted but not removed:
553+
Delete returns a `DeleteDataResponse`: the `object` name, the record `id`, and a
554+
`success` flag.
570555

571556
```json
572557
{
573-
"success": true,
574-
"data": {
575-
"id": "acc_123",
576-
"deleted": true,
577-
"deleted_at": "2024-01-16T12:00:00Z",
578-
"deleted_by": "user_456"
579-
}
558+
"object": "account",
559+
"id": "acc_123",
560+
"success": true
580561
}
581562
```
582563

564+
<Callout type="warn">
565+
`enable.trash` exists as an object flag, but there is currently **no soft-delete /
566+
recycle-bin runtime** (ADR-0049): `DELETE` performs a hard delete and returns the
567+
`{ object, id, success }` shape above. Records are removed permanently — there are no
568+
`deleted_at` / `deleted_by` fields and no restore semantics.
569+
</Callout>
570+
583571
**Constraint Violations:**
584572
Database constraint failures are surfaced as structured errors. For example, a
585573
unique-constraint violation returns HTTP 409:
@@ -677,10 +665,14 @@ lists items of that type. Types are singular (`object`, `view`, `app`, …), so
677665
objects are listed at `/api/v1/meta/object`.
678666

679667
**Response:**
668+
669+
A type listing returns `{ type, items }` — the requested metadata `type` plus the
670+
`items` array of matching entries.
671+
680672
```json
681673
{
682-
"success": true,
683-
"data": [
674+
"type": "object",
675+
"items": [
684676
{
685677
"name": "account",
686678
"label": "Account",
@@ -709,10 +701,15 @@ Authorization: Bearer <token>
709701
```
710702

711703
**Response:**
704+
705+
A single-item read returns `{ type, name, item }` — the metadata `type`, the item
706+
`name`, and the full schema under `item`.
707+
712708
```json
713709
{
714-
"success": true,
715-
"data": {
710+
"type": "object",
711+
"name": "account",
712+
"item": {
716713
"name": "account",
717714
"label": "Account",
718715
"plural_label": "Accounts",
@@ -877,8 +874,9 @@ GET /api/data/account?select=id,name,status
877874
### Use Expand for Relations
878875
**Bad:** N+1 queries
879876
```javascript
880-
const tasks = await fetch('/api/data/task');
881-
for (const task of tasks.data) {
877+
const res = await fetch('/api/data/task');
878+
const { records } = await res.json();
879+
for (const task of records) {
882880
task.assignee = await fetch(`/api/data/user/${task.assignee_id}`);
883881
}
884882
```

content/docs/ui/create-vs-edit-form.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,8 @@ Rule of thumb: **"different field subset" → derive. "different layout or flow"
101101

102102
## Runnable example
103103

104-
- Object: [`examples/app-showcase/src/objects/contact.object.ts`](https://github.com/objectstack-ai/framework/blob/main/examples/app-showcase/src/objects/contact.object.ts) — flat, grouped, intent-tagged field set.
105-
- Views: [`examples/app-showcase/src/views/contact.view.ts`](https://github.com/objectstack-ai/framework/blob/main/examples/app-showcase/src/views/contact.view.ts) — full edit form + sparse `formViews.create` + `addRecord` binding.
104+
- Object: [`examples/app-showcase/src/data/objects/contact.object.ts`](https://github.com/objectstack-ai/framework/blob/main/examples/app-showcase/src/data/objects/contact.object.ts) — flat, grouped, intent-tagged field set.
105+
- Views: [`examples/app-showcase/src/ui/views/contact.view.ts`](https://github.com/objectstack-ai/framework/blob/main/examples/app-showcase/src/ui/views/contact.view.ts) — full edit form + sparse `formViews.create` + `addRecord` binding.
106106

107107
## Anti-patterns
108108

content/docs/ui/dashboards.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ A **Dashboard** defines an analytics page with chart widgets, key metrics, and d
1111

1212
Every widget binds to a **dataset** (the semantic layer, ADR-0021) and selects
1313
the dataset's `dimensions` and `values` by name. The dataset owns the base
14-
object, joins, and certified measures, so the same numbers stay consistent
14+
object, joins, and measures, so the same numbers stay consistent
1515
across every dashboard and report.
1616

1717
```typescript
@@ -111,7 +111,7 @@ selects `dimensions` (X / group / split) and `values` (the measures to plot):
111111

112112
Every widget binds to a **dataset** — the single semantic layer (ADR-0021).
113113
A dataset defines the base object, allowed relationship joins, reusable
114-
dimensions, certified measures, and intrinsic filters once; dashboards and
114+
dimensions, measures, and intrinsic filters once; dashboards and
115115
reports then reference the same names so "revenue", "pipeline", or "win rate"
116116
stay consistent across surfaces.
117117

@@ -132,7 +132,7 @@ export const salesDataset = defineDataset({
132132
{ name: 'close_month', field: 'close_date', type: 'date', dateGranularity: 'month' },
133133
],
134134
measures: [
135-
{ name: 'revenue', field: 'amount', aggregate: 'sum', certified: true },
135+
{ name: 'revenue', field: 'amount', aggregate: 'sum' },
136136
{ name: 'deal_count', aggregate: 'count' },
137137
],
138138
});

0 commit comments

Comments
 (0)