Skip to content

Commit 88afb49

Browse files
os-zhuangclaude
andcommitted
docs: P0 audit fixes — resolve contradictions, hand over the keys
Fixes the highest-leverage findings from the full docs audit (110 guide pages reviewed against a customer-comprehension rubric). Contradiction fixes (docs said two different things, or taught dead APIs): - OWD: permissions-matrix table/example/callout now use the canonical four sharingModel values (ADR-0090 D4) instead of removed legacy aliases - referenceFilters taught as a positive example while relationships.mdx documents it as schema-accepted-but-inert — fields.mdx now teaches structured lookupFilters + dependsOn; property tables mark it Legacy - percent semantics unified to fraction storage (0.85 = 85%) across fields/decision-tree (validation-rules already had it right) - hook input shape unified to the `{ doc }` contract (plugins tutorial handler + test now match kernel/events.mdx) - libsql:// removed from self-hosting env table (driver ships in Cloud, not the open framework); backup page Turso section qualified - api/index MCP row: actions require the author's ai.exposed opt-in - permission-metadata: `positions` (not `roles`) is the canonical field - common-patterns: all pattern objects now declare sharingModel and the page warns about namespace prefixes — examples pass the gate they teach Key handovers (previously undocumented dead-ends): - build-with-claude-code: seeded dev admin credentials at the verify step, npx os invocations, localhost MCP connect example - api/index: new Authentication section (session sign-in + API keys) - self-hosting: "First boot: create the admin" section (first signup = bootstrap admin); fixed dead examples/docker links (also docker/README) - vercel: warn that InMemoryDriver loses data on every cold start - getting-started prerequisites: Node 18+ only for the scaffold path - kernel/services-checklist: stale-status warning banner - quick-reference: unlink two non-existent reference pages Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 709f97e commit 88afb49

18 files changed

Lines changed: 126 additions & 40 deletions

content/docs/api/index.mdx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ ObjectStack exposes a fully typed REST API. All endpoints use JSON request/respo
1515
| :--- | :--- |
1616
| **REST** | ✅ Auto-generated from the protocol (`@objectstack/rest`) — CRUD, query, batch, metadata, packages |
1717
| **Realtime** | ⚠️ In-process pub/sub service (`@objectstack/service-realtime`, single-instance); the `/realtime/*` REST routes and WebSocket/SSE transport are plugin-provided — none ships in the open framework |
18-
| **MCP** | ✅ Non-system objects and their actions exposed as Model Context Protocol tools, gated by the caller's permissions/RLS rather than a per-action opt-in flag ([AI module](/docs/ai)) |
18+
| **MCP** | ✅ Non-system objects exposed automatically as Model Context Protocol tools; actions additionally require the author's `ai.exposed` opt-in — every call is gated by the caller's permissions/RLS ([AI module](/docs/ai)) |
1919
| **GraphQL** | ⚠️ Route is wired but **bring-your-own service**: `/graphql` returns 501 unless an implementation of the `IGraphQLService` contract is registered — none ships in the open framework |
2020
| **OData** | ⚠️ Vocabulary only: REST list endpoints accept OData-style operators (e.g. `$top`), but there is no standalone OData endpoint |
2121

@@ -58,6 +58,23 @@ sequenceDiagram
5858
See [Actions as Tools](/docs/ai/actions-as-tools) for the `run_action` bridge and the
5959
[MCP reference](/docs/references/ai/mcp) for binding external MCP servers into your agents.
6060

61+
## Authentication
62+
63+
Every REST call runs as a principal — anonymous requests only see what your
64+
permission model grants anonymous users. Two ways to authenticate:
65+
66+
- **Session cookie** (browsers, quick local tests): `POST /api/v1/auth/sign-in/email`
67+
with `{ "email": "…", "password": "…" }` sets the session cookie — reuse it with
68+
`curl -c cookies.txt` / `-b cookies.txt`. On a fresh dev database the seeded
69+
admin is `admin@objectos.ai` / `admin123`.
70+
- **API key** (scripts, CI, headless agents): mint one with `POST /api/v1/keys`
71+
(the key is shown once), or from **Setup → Connect an Agent** in the Console.
72+
Send it as `x-api-key: osk_…` or `Authorization: Bearer osk_…`.
73+
74+
See [Authentication](/docs/permissions/authentication) for the full identity
75+
surface (OAuth flows, sessions, providers) and
76+
[Plugin Endpoints](/docs/api/plugin-endpoints) for the auth route catalog.
77+
6178
## What's in this module
6279

6380
- [Data API](/docs/api/data-api) — CRUD, batch operations, record cloning, and analytics queries

content/docs/data-modeling/field-type-decision-tree.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ flowchart TD
146146
|:---|:---|:---|
147147
| `number` | Any numeric value (set `precision`/`scale` for decimals) | `price`, `weight`, `quantity` |
148148
| `currency` | Money amounts with currency code | `amount`, `total_price` |
149-
| `percent` | Percentage values (0–100 or 0–1) | `completion`, `discount` |
149+
| `percent` | Percentage values, stored as fractions 0–1 (`0.85` = 85%) | `completion`, `discount` |
150150
| `rating` | Star ratings (typically 1–5) | `satisfaction`, `difficulty` |
151151
| `slider` | Numeric value via slider UI | `volume`, `priority_level` |
152152

content/docs/data-modeling/field-types.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ Reference to a record in another object (foreign key).
301301
| Property | Type | Default | Description |
302302
|:---|:---|:---|:---|
303303
| `reference` | `string` | **required** | Target object name (snake_case) |
304-
| `referenceFilters` | `string[]` || Filters applied to lookup dialogs (e.g. `"active = true"`) |
304+
| `referenceFilters` | `string[]` || **Legacy** — accepted by the schema but not read by the record-picker (filters nothing). Use structured `lookupFilters` + `dependsOn` instead; see [Relationships](/docs/data-modeling/relationships) |
305305
| `deleteBehavior` | `'restrict' \| 'cascade' \| 'set_null'` | `'set_null'` | Behavior when referenced record is deleted (a *required* lookup left at the default `set_null` is escalated to `restrict`, since a NOT NULL foreign key cannot be cleared) |
306306

307307
```typescript
@@ -323,7 +323,7 @@ Parent-child relationship (cascading delete by default).
323323
| Property | Type | Default | Description |
324324
|:---|:---|:---|:---|
325325
| `reference` | `string` | **required** | Target (master) object name |
326-
| `referenceFilters` | `string[]` || Filters applied to lookup dialogs (e.g. `"active = true"`) |
326+
| `referenceFilters` | `string[]` || **Legacy** — accepted by the schema but not read by the record-picker (filters nothing). Use structured `lookupFilters` + `dependsOn` instead; see [Relationships](/docs/data-modeling/relationships) |
327327
| `deleteBehavior` | `'restrict' \| 'cascade' \| 'set_null'` | `'cascade'` | Behavior when parent is deleted (master-detail cascades unless set to `restrict`) |
328328
| `inlineEdit` | `boolean \| 'grid' \| 'form'` || Edit child records inline on the parent create/edit form (`true` = auto-pick, `'grid'`, or `'form'`) |
329329
| `inlineColumns` | `array` || Optional explicit inline grid columns |

content/docs/data-modeling/fields.mdx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ notes: Field.markdown({ label: 'Notes' }),
6363
```typescript
6464
quantity: Field.number({ label: 'Quantity', min: 0, max: 10000, step: 1 }),
6565
price: Field.currency({ label: 'Price', scale: 2, min: 0 }),
66-
discount: Field.percent({ label: 'Discount', scale: 2, min: 0, max: 100 }),
66+
// percent stores a fraction: 0.15 = 15% (the UI renders it as a percentage)
67+
discount: Field.percent({ label: 'Discount', scale: 2, min: 0, max: 1 }),
6768
```
6869

6970
**Currency Configuration:**
@@ -145,10 +146,13 @@ account: Field.lookup('account', {
145146
required: true,
146147
}),
147148

148-
// Filtered lookup
149+
// Filtered lookup — structured filters, cascading from another field
149150
primary_contact: Field.lookup('contact', {
150151
label: 'Primary Contact',
151-
referenceFilters: ['account = {account}', 'is_active = true'],
152+
dependsOn: ['account'],
153+
lookupFilters: [
154+
{ field: 'is_active', operator: 'eq', value: true },
155+
],
152156
}),
153157

154158
// Master-detail (cascade delete)

content/docs/data-modeling/validation-rules.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ These properties apply to **all** field types and are validated by the base `Fie
244244
| Property | Type | Default | Validation Behavior |
245245
|:---|:---|:---|:---|
246246
| `reference` | `string` || **Required.** Target object name |
247-
| `referenceFilters` | `string[]` || Filter expressions for lookup dialogs |
247+
| `referenceFilters` | `string[]` || **Legacy** — schema-accepted but not read by the record-picker; use `lookupFilters` + `dependsOn` |
248248
| `deleteBehavior` | `enum` | `set_null` | `set_null`, `cascade`, or `restrict` |
249249
| `multiple` | `boolean` | `false` | Allow multiple references |
250250

content/docs/deployment/backup-restore.mdx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,11 @@ sqlite3 /srv/data/app.db ".backup '/backups/app-$(date +%F).db'"
5757
The default location when `OS_DATABASE_URL` is unset is
5858
`<home>/data/objectstack.db` under the ObjectStack home directory.
5959

60-
### Turso / libSQL and managed databases
60+
### Managed databases (hosted Postgres, MongoDB Atlas, …)
6161

6262
Use the provider's snapshot, branching, or point-in-time-restore facilities.
63-
Nothing ObjectStack-specific applies.
63+
Nothing ObjectStack-specific applies. (Turso/libSQL applies only to ObjectStack
64+
Cloud-managed environments — the open framework does not ship that driver.)
6465

6566
## Backing up uploaded files
6667

content/docs/deployment/self-hosting.mdx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ workable default:
3232

3333
| Variable | Why it must be set |
3434
|:---|:---|
35-
| `OS_DATABASE_URL` | Without it, data lands in a SQLite file under the ObjectStack home directory (`~/.objectstack`, or `<cwd>/.objectstack` next to a project config) — fine for one box, wrong for containers. Use `postgres://…`, `libsql://…`, or a mounted `file:…` path. |
35+
| `OS_DATABASE_URL` | Without it, data lands in a SQLite file under the ObjectStack home directory (`~/.objectstack`, or `<cwd>/.objectstack` next to a project config) — fine for one box, wrong for containers. Use `postgres://…`, `mongodb://…`, or a mounted `file:…` path (`libsql://` / Turso is **not** supported by the open framework — that driver ships in ObjectStack Cloud). |
3636
| `OS_AUTH_SECRET` | Session secret for the auth plugin (`AUTH_SECRET` is the legacy alias). Without it, `/api/v1/auth/*` is **silently skipped** — the server runs unauthenticated. |
3737
| `OS_SECRET_KEY` | 32-byte master key encrypting every stored secret (`openssl rand -hex 32`). On a container's ephemeral filesystem the auto-minted key is **lost on restart**, making previously-encrypted secrets undecryptable. |
3838
| `OS_PORT` | `os start` **fails loudly** if the port is busy (it never auto-shifts like `os dev`). Pin it and keep your reverse-proxy upstream in sync. |
@@ -117,7 +117,7 @@ straight from release storage instead of a mount.)
117117
For a self-contained deployable image, extend it. The Dockerfile below (plus
118118
the compose stack in the next section and a `.dockerignore`) ships
119119
ready-to-copy in
120-
[`examples/docker`](https://github.com/objectstack-ai/framework/tree/main/examples/docker)
120+
[`docker/`](https://github.com/objectstack-ai/framework/tree/main/docker)
121121
— drop the files into your scaffolded project.
122122

123123
```dockerfile title="Dockerfile"
@@ -314,6 +314,20 @@ decrypt each other's secrets. All replicas must share the same
314314
`OS_SECRET_KEY`, `OS_AUTH_SECRET`, and database. See
315315
[Cluster](/docs/kernel/cluster).
316316

317+
## First boot: create the admin
318+
319+
On a fresh production database there are no users yet. Open the deployment's
320+
root URL and **sign up — the very first account to register becomes the
321+
bootstrap admin** (this works even with `OS_DISABLE_SIGNUP=true`, which only
322+
blocks sign-ups after that first account exists). Do this immediately after
323+
the first deploy, before sharing the URL; then create your real user accounts
324+
and lock sign-up down via `OS_AUTH_SIGNUP_ENABLED` /
325+
[SSO](/docs/permissions/sso) as policy dictates.
326+
327+
Note the production server seeds **no** dev credentials — the
328+
`admin@objectos.ai` / `admin123` account you may know from `os dev` exists only
329+
on empty development databases.
330+
317331
## Go-live
318332

319333
Before pointing real users at the deployment, walk the

content/docs/deployment/vercel.mdx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,13 @@ export async function ensureApp(): Promise<Hono> {
7878
}
7979
```
8080

81+
<Callout type="warn">
82+
`InMemoryDriver` keeps this snippet self-contained, but on serverless **all data
83+
is lost on every cold start** — fine for a demo, wrong for anything real. Before
84+
going past a proof of concept, swap the driver for a real database (see
85+
[Database Drivers](/docs/data-modeling/drivers)) and point `OS_DATABASE_URL` at it.
86+
</Callout>
87+
8188
**2. Create the API entrypoint** (`api/index.ts`):
8289

8390
```typescript

content/docs/getting-started/build-with-claude-code.mdx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -257,11 +257,12 @@ Validation proves the metadata is *well-formed*. It can't prove the app does wha
257257
*you* meant. That's the human half of the loop: **run it and look.**
258258

259259
```bash
260-
os dev --ui
260+
npx os dev --ui
261261
```
262262

263-
Open [http://localhost:3000/_console/](http://localhost:3000/_console/) and drive
264-
the app like a user:
263+
Open [http://localhost:3000/_console/](http://localhost:3000/_console/) and sign in
264+
as the dev admin the server seeds on an empty database
265+
(`admin@objectos.ai` / `admin123`). Then drive the app like a user:
265266

266267
- Create a ticket. Confirm the fields, labels, and picklist colors match intent.
267268
- Check the **Resolve** action shows on an open ticket — and **disappears** once
@@ -316,7 +317,12 @@ its own OAuth 2.1 authorization server, so interactive clients just open a
316317
browser login (you connect as yourself, no admin-minted credentials):
317318

318319
```bash
320+
# the dev server you just booted in step 5
321+
claude mcp add --transport http support-desk http://localhost:3000/api/v1/mcp
322+
323+
# or a deployed instance
319324
claude mcp add --transport http support-desk https://your-deployment.example.com/api/v1/mcp
325+
320326
# first tool use opens a browser login — you're connected as yourself
321327
```
322328

content/docs/getting-started/common-patterns.mdx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,15 @@ This guide covers the most common patterns you will use when building applicatio
1111
**Import:** `import { defineStack, defineView, defineApp, defineFlow, defineAgent } from '@objectstack/spec'`
1212
</Callout>
1313

14+
<Callout type="warn">
15+
**Before you copy:** the examples use short object names (`project`, `order`) for
16+
readability. In a real project, `os validate` additionally enforces your project's
17+
**namespace prefix** on every object name (`my_app_project`) and an explicit
18+
**`sharingModel`** on every custom object (ADR-0090) — the patterns below include
19+
`sharingModel`; remember to add your prefix. See
20+
[Validating Metadata](/docs/getting-started/validating-metadata).
21+
</Callout>
22+
1423
---
1524

1625
## 1. CRUD Object Definition
@@ -27,6 +36,7 @@ export default defineStack({
2736
{
2837
name: 'project',
2938
label: 'Project',
39+
sharingModel: 'public_read_write',
3040
fields: {
3141
title: { label: 'Title', type: 'text', required: true, maxLength: 200 },
3242
description: { label: 'Description', type: 'textarea' },
@@ -53,6 +63,7 @@ export default defineStack({
5363
objects: {
5464
project: {
5565
label: 'Project',
66+
sharingModel: 'public_read_write',
5667
fields: {
5768
title: { label: 'Title', type: 'text', required: true, maxLength: 200 },
5869
description: { label: 'Description', type: 'textarea' },
@@ -85,6 +96,7 @@ Create a parent-child relationship between a master and its detail records. Note
8596
{
8697
name: 'order',
8798
label: 'Order',
99+
sharingModel: 'public_read',
88100
fields: {
89101
order_number: { label: 'Order #', type: 'autonumber', autonumberFormat: 'ORD-{0000}' },
90102
customer: { label: 'Customer', type: 'lookup', reference: 'contact' },
@@ -106,8 +118,10 @@ Create a parent-child relationship between a master and its detail records. Note
106118
}
107119
},
108120
{
121+
// Master-detail child: access is derived from the parent order record
109122
name: 'order_line',
110123
label: 'Order Line',
124+
sharingModel: 'controlled_by_parent',
111125
fields: {
112126
order: {
113127
label: 'Order',
@@ -265,6 +279,7 @@ Define a multi-step approval flow for records.
265279
objects: [{
266280
name: 'expense_report',
267281
label: 'Expense Report',
282+
sharingModel: 'private',
268283
fields: {
269284
title: { label: 'Title', type: 'text', required: true },
270285
amount: { label: 'Amount', type: 'currency' },
@@ -402,6 +417,7 @@ Restrict field visibility and editability based on user profiles.
402417
objects: [{
403418
name: 'employee',
404419
label: 'Employee',
420+
sharingModel: 'private',
405421
fields: {
406422
name: { label: 'Name', type: 'text', required: true },
407423
email: { label: 'Email', type: 'email', required: true },

0 commit comments

Comments
 (0)