From 6bd5ee8c9c74afa021b93edab6ee12bc26b2f659 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 2 Sep 2026 06:46:33 +0000 Subject: [PATCH 1/3] skills(api): cut other packages' anchors and the dead RestApiEndpointSchema example; teach http.server mount, the ADR-0112 code ledger and OCC Optimization flight over skills/objectstack-api/SKILL.md (audit record posted on the card). Shrink-only, additions paid by deletions in the same file. DELETE / MERGE / REWRITE API-E-01 the leading os:check example taught RestApiEndpointSchema: 0 consumers repo-wide, no plugin-rest-api package, and RestApiPluginConfig has no `endpoints` key. The one live fact (auth is the flat public + permissions pair) folded into the auth section. API-C-01 datasources + driver table -> objectstack-data rules/datasources.md and objectstack-platform "Driver Selection Guide" (both carry more than this file did, including the Turso Cloud/EE caveat). API-C-02 inter-service communication -> objectstack-platform rules/service-registry.md. Removes the `declare const kernel` block that type-checked against a fabricated host shape. API-C-03 Security Layers table named no authorable key -> one boundary line. API-D-01 health/readiness response bodies -> one line (F-05, A-03). API-D-02 Handler Status table, neutralised by its own callout. API-D-03 Best Practices 1 and 4 (duplicate / no key, no gate). API-D-04 Pitfalls 1, 2, 4 (FLS has no authorable spelling; the page sizes match no platform constant; rate limiting is stated at D6). API-D-05 the carve-out why-prose; the rule stays. API-B-01 third rendering of "no nested auth block". API-B-02 second gate invocation (and the `objectstack validate` spelling, API-G-01) -> Verify your work states it once. API-B-03 the apiMethods primitives/derived rule, stated 3x -> once. The `## API Methods (Operations)` heading and all six ApiMethod members stay: it is a registered exhaustive section (binding `api-methods` in scripts/check-skill-identifier-liveness.mjs). API-B-05 preamble restating the frontmatter. API-F-04 environment-scoped routes / projectResolution, 0 usages (G-03). API-F-05 ServiceInfo, a response shape with 0 usages -> one line. API-F-06 the two declared-but-unenforced realtime notes (filed separately). API-A-01 "When to Use This Skill" -> Skill Boundaries table, matching the objectstack-data / objectstack-query form. API-A-02 frontmatter drops the "versioning" promise: versioning.zod.ts has zero consumers outside a pin test. API-A-03 the description forbids the client seat; the body taught it. ADD (funded) API-F-01 the http.server code-mount example, the only code-route pattern the repo ships. Left UNMARKED: `ctx` is @objectstack/core's PluginContext and the skills os:check surface resolves @objectstack/spec only, so a marker would need a locally declared stand-in -- the same thing API-C-02 was deleted for. Measured correction to the audit: ctx.getService is SYNCHRONOUS and throws (0 awaited call sites in packages/plugins, 112 sync). API-F-02 the ADR-0112 two-tier error-code ledger and the downstream envelopeViolations + makeApiErrorSchema composition. API-F-03 OCC stated with its real spellings: If-Match header or expectedVersion body field, body wins, 409 CONCURRENT_UPDATE. 6311 -> 4635 tokens (ceiling 6319, unchanged); 607 -> 429 lines. Part of #14304 Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1 --- skills/objectstack-api/SKILL.md | 399 +++++++++----------------------- 1 file changed, 110 insertions(+), 289 deletions(-) diff --git a/skills/objectstack-api/SKILL.md b/skills/objectstack-api/SKILL.md index c73e669971..99267f6004 100644 --- a/skills/objectstack-api/SKILL.md +++ b/skills/objectstack-api/SKILL.md @@ -3,7 +3,7 @@ name: objectstack-api description: > Design the server-side API surface that an ObjectStack runtime exposes — REST endpoints, auth providers, realtime channels, error envelopes, - batch/versioning contracts. Use when the user is adding `*.endpoint.ts`, + batch contracts. Use when the user is adding `*.endpoint.ts`, configuring auth providers, defining custom routes, or extending the REST generator. Do not use for: consuming an ObjectStack API from a client (that is just standard HTTP — no skill needed); the auto-generated @@ -21,21 +21,14 @@ metadata: # API Design — ObjectStack API Protocol -Expert instructions for designing REST APIs, service contracts, and -integration protocols using the ObjectStack specification. This skill covers -endpoint definitions, API discovery, authentication, dispatcher configuration, -and inter-service communication patterns. +## Skill Boundaries ---- - -## When to Use This Skill - -- You are defining **custom REST API endpoints** beyond auto-generated CRUD. -- You need to configure **API authentication and authorization**. -- You are setting up **service discovery** and health checks. -- You are designing **inter-service communication** (service-to-service calls). -- You need to understand the **dispatcher routing** system. -- You are integrating **external APIs** via datasource connectors. +| Need | Use instead | +|:-----|:------------| +| Model objects, fields, permissions, or datasources | **objectstack-data** | +| Filter, sort, paginate, or aggregate a request | **objectstack-query** | +| Register a kernel service, or read one from a plugin | **objectstack-platform** | +| CEL in a route guard or auth predicate | **objectstack-formula** | --- @@ -63,12 +56,8 @@ aggregation goes through `POST /api/v1/data/{object}/query` with `groupBy`/`aggregations` in the body. > **Key rule:** If your object defines `apiMethods`, only those operations (and -> what derives from them) are exposed. For example, `apiMethods: ['get', 'list']` -> creates a read-only API. The authorable values are the SIX PRIMITIVES -> (`get/list/create/update/delete/bulk`); everything else (`export`, -> `search`, `upsert`, …) is DERIVED from them by the server — `['list']` grants -> aggregate/search/export for free, `['create','update']` grants upsert/import. -> An empty array `[]` means deny-all (fully closed). +> what derives from them) are exposed; `[]` means deny-all. The authorable +> values are the six primitives — see **API Methods (Operations)**. ### Metadata API (`/meta`) @@ -80,18 +69,8 @@ GET /api/v1/meta/:type # List metadata items of a type (object, view, GET /api/v1/meta/:type/:name # Read a single metadata item ``` -Three query-param contracts: - -- **`?preview=draft`** — overlay pending **draft** metadata instead of the - published copy, on both list and get. The draft path is **cache-bypassed**, so - it always reflects the latest unpublished edit (the authoring loop). -- **`?package=`** — **package-scope** a read so - two installed packages that share a bare metadata name disambiguate by owning - package; prefer-local resolution. A package-scoped read **bypasses the meta - cache**. The layered / Studio-editor read is package-scoped the same way. -- **`/meta/doc`** — docs-as-metadata. The **list** response omits - each doc's `content` by default (use `?include=content` to include it); the - **single-item** `GET /meta/doc/:name` always returns the full body. +The read-side query params (`?preview=draft`, `?package=`, `?include=content`) +are **client** contracts — nothing in a stack declares them. ### Public (anonymous) Form Endpoints @@ -114,36 +93,6 @@ strips fields outside the form's `sections[].fields[]` list; a `node_modules/@objectstack/spec/src/ui/sharing.zod.ts` (`SharingConfigSchema` with `allowAnonymous` / `publicLink`). -### Custom Endpoints - -For business logic beyond CRUD, define custom endpoints via the REST API -plugin (`RestApiEndpointSchema`): - - -```typescript -import { RestApiEndpointSchema, type RestApiEndpoint } from '@objectstack/spec/api'; - -export const closeCase: RestApiEndpoint = RestApiEndpointSchema.parse({ - method: 'POST', - path: '/api/v1/cases/:id/close', - handler: 'closeCase', // protocol method / handler identifier - category: 'data', - description: 'Close a support case with resolution notes.', - public: false, // auth required (the default) - permissions: ['support_agent'], - requestSchema: 'CloseCaseRequest', // schema *name* reference, not an inline shape - responseSchema: 'SupportCase', - handlerStatus: 'implemented', -}); -``` - -There is no `name`, `request`, `response`, or `auth` field on this schema — -request/response schemas are referenced **by name** (`requestSchema` / -`responseSchema`), and auth is the flat `public` + `permissions` pair. - -The alternative — and usually the better one — is the **declarative** surface -`ApiEndpointSchema`, which needs no handler code at all. See the next section. - --- ## Declarative Endpoints (`apis:`) — no handler code @@ -187,31 +136,19 @@ export const leadFeed: ApiEndpoint = { ### The path carve-out (ADR-0121 D1/D2) A declared path must be `/api/v1/apps//`. Only the -subpath is yours to name. `manifest.namespace` must be declared **explicitly** — -there is deliberately no derivation from `manifest.id`, because an outward URL -contract must not move because a package id was rewritten. This is what makes -route ownership structural: no built-in domain lives under `apps/`, and two -packages cannot collide because their namespaces differ. - -Note the ordinary naming rules above still apply *inside* the subpath, but the -prefix is not yours to choose — a path outside the carve-out is rejected at -publish, and would match nothing at runtime even if it were not. +subpath is yours to name, and the ordinary naming rules below apply inside it. +`manifest.namespace` must be declared **explicitly** — it is never derived from +`manifest.id`. A path outside the carve-out is rejected at publish. ### Five publish gates, each with a prescription -A declaration this runtime cannot serve is **rejected at publish**, one gate at a -time, each naming the endpoint, the key and the fix. Run the gate yourself: - -```bash -objectstack validate # or: os build — same gates -``` - -Do not memorise the gate texts; **read the rejection**, it carries the fix. -What the five gates cover: **namespace** (the carve-out above), -**supported target** (`script` / `proxy` do not execute in 17.x; an -`object_operation` needs both `objectParams.object` and `.operation`; a `flow` -needs a `target`), **mapping** (below), **policy** (below), and **uniqueness** -(one `METHOD` + path claim per stack). +A declaration this runtime cannot serve is **rejected at publish**, one gate at +a time. Do not memorise the gate texts; **read the rejection**, it carries the +fix. The five: **namespace** (the carve-out above), **supported target** +(`script` / `proxy` do not execute in 17.x; an `object_operation` needs both +`objectParams.object` and `.operation`; a `flow` needs a `target`), +**mapping** (below), **policy** (below), and **uniqueness** (one `METHOD` + +path claim per stack). ### `authRequired` and the D6 pairing @@ -255,6 +192,32 @@ for `authRequired: false`. --- +## Code routes: mounting on `http.server` + +The one code-route pattern the platform ships: + +```ts +import type { IHttpServer } from '@objectstack/spec/contracts'; + +// in a plugin's `start(ctx)`. ONE `try` PER NAME: `getService` is SYNCHRONOUS +// and THROWS on an empty slot, so `a() ?? b()` in one `try` never reaches `b`. +const read = (n: string): IHttpServer | null => { + try { return ctx.getService(n); } catch { return null; } +}; +ctx.hook('kernel:ready', () => { // NOT later — see below + const http = read('http.server') ?? read('http-server'); // canonical FIRST + http?.post('/api/v1/apps/acme/recalc', (req, res) => { res.status(200).json({}); }); +}); +``` + +`http-server` is the deprecated alias and is absent on the provider path that +registers no alias, so an alias-only read mounts nothing there. `listen()` is +deferred to `kernel:listening` so late registration still lands — Hono seals +its matcher on the first matched request and a later `post()` throws. Service +resolution itself is objectstack-platform → `rules/service-registry.md`. + +--- + ## Endpoint Naming Conventions | Pattern | Use Case | Example | @@ -272,20 +235,13 @@ for `authRequired: false`. - Use **verbs** only for actions, not for CRUD (`/close`, `/approve`). - Always prefix with `/api/v1/` for versioning. -Depending on deployment configuration, routes may also be mounted -**environment-scoped** under `/api/v1/environments/:environmentId/...` -(project scoping in the REST server). With `projectResolution: 'required'` -only the scoped routes are registered; with `optional`/`auto` the bare -`/api/v1/...` routes remain available alongside them. - --- ## API Methods (Operations) -The authorable `ApiMethod` enum is the SIX PRIMITIVES. The wider -EFFECTIVE operation vocabulary (`ApiOperation`, 14 values) is what gates and -responses speak — the eight extra verbs are DERIVED from the primitives, never -declared in `apiMethods`: +The authorable `ApiMethod` enum is the SIX PRIMITIVES. The wider EFFECTIVE +vocabulary (`ApiOperation`, 14 values) is what gates and responses speak; its +eight extra verbs are never declared in `apiMethods`: **Authorable primitives:** @@ -300,78 +256,31 @@ declared in `apiMethods`: **Derived operations (granted automatically, never authored):** -| Operation | Derives from | HTTP surface today | Purpose | -|:----------|:-------------|:-------------------|:--------| -| `upsert` | `create` ∧ `update` | No dedicated generated route in `@objectstack/rest` today | Create or update by external ID | -| `aggregate` | `list` | No dedicated route — use `POST /data/{object}/query` with `groupBy`/`aggregations` | Count, sum, avg, min, max | -| `history` | `get` ∧ `trackHistory` | Gating only — no dedicated generated route today | Audit trail access | -| `search` | `list` ∧ `searchable` | Global `GET /api/v1/search` (cross-object), not per-object | Full-text search | -| `restore` | never (trash retired) | Gating only | Restore a soft-deleted record (reserved — platform deletes are hard today) | -| `purge` | never (trash retired) | Gating only | Permanent deletion | -| `import` | `create` ∨ `update` (writeMode-precise) | `POST /data/{object}/import` | Bulk data import | -| `export` | `list` | `GET /data/{object}/export` | Data export | - ---- - -## Service Discovery - -ObjectStack services register themselves with the kernel and expose discovery -metadata. +| Operation | Derives from | HTTP surface today | +|:----------|:-------------|:-------------------| +| `upsert` | `create` ∧ `update` | none generated | +| `aggregate` | `list` | `POST /data/{object}/query` with `groupBy`/`aggregations` | +| `search` | `list` ∧ `searchable` | global `GET /api/v1/search`, not per-object | +| `import` | `create` ∨ `update` (writeMode-precise) | `POST /data/{object}/import` | +| `export` | `list` | `GET /data/{object}/export` | -### Service Info Schema +`history` (from `get` ∧ `trackHistory`) gates only. `restore` / `purge` never +derive — the trash surface is retired. Declaring any derived verb in +`apiMethods` is stripped at parse with a FROM → TO warning. -The discovery response (`GET /api/v1/discovery`) reports each registered -service in a `services` **record** — the record key is the service name, and -there is no `endpoints` array on a service entry: - - -```typescript -import type { ServiceInfo } from '@objectstack/spec/api'; - -// In the discovery response: services: { data: { ... }, ... } -const dataService: ServiceInfo = { - enabled: true, // required - status: 'available', // 'available' | 'registered' | 'unavailable' | 'degraded' | 'stub' - handlerReady: true, // HTTP handler verified mounted (omitted = unknown) - route: '/api/v1/data', - provider: 'objectql', - version: '1.0.0', -}; -``` +--- -Optional fields also include `message` (human-readable reason if unavailable) -and `rateLimit` (per-service quota info). There is no `healthy`/`unhealthy` -status — `available` is the fully-operational state. - -### Health Endpoint - -Every ObjectStack deployment exposes `GET /api/v1/health`, which returns the -standard success envelope (no per-service map): - -```json -{ - "success": true, - "data": { - "status": "ok", - "timestamp": "2026-07-20T12:00:00.000Z", - "version": "1.0.0", - "uptime": 42.7 - } -} -``` +## Discovery & Health -A readiness probe also exists at `GET /ready` on the same base path — it -returns 200 only when the kernel is fully running, and 503 while booting or -shutting down. For per-service status, use `GET /api/v1/discovery` (the -`services` record above). +`GET /api/v1/health`, `GET /ready` (200 running / 503 booting) and +`GET /api/v1/discovery` (per-service status) exist on **every** deployment. +All three are **response** surfaces — nothing to author. --- -## Dispatcher & Routing +## Dispatcher Error Codes -The **HttpDispatcher** is the central request router in ObjectStack. - -### Dispatcher Error Codes +The **HttpDispatcher** is the central request router; it answers: | HTTP Status | Error Code | When | |:------------|:-----------|:-----| @@ -380,20 +289,6 @@ The **HttpDispatcher** is the central request router in ObjectStack. | 501 | `NOT_IMPLEMENTED` | Route declared but handler is a stub | | 503 | `SERVICE_UNAVAILABLE` | Service is registered but not ready | -### Handler Status - -Every endpoint has a handler status: - -| Status | Meaning | -|:-------|:--------| -| `implemented` | Handler is fully functional | -| `stub` | Handler exists but returns mock data | -| `planned` | Handler is defined in the spec but not yet coded | - -> **Best practice:** `handlerStatus` is DOCUMENTATION — nothing reads it at -> runtime. The dispatcher's `501 NOT_IMPLEMENTED` comes from the endpoint -> executor, never from this field. - --- ## Realtime Subscriptions @@ -403,36 +298,27 @@ Realtime contracts are pointer-style — read the spec source for exact shapes: - `node_modules/@objectstack/spec/src/api/realtime.zod.ts` — `TransportProtocol` (`websocket` | `sse` | `polling`), `SubscriptionSchema` (`id`, `events[]`, `transport`, optional `channel`), `RealtimeEventSchema`, and - `RealtimeConfigSchema`. Note: the `RealtimeEventType` enum is declared but - not yet enforced — the runtime emits `data.record.*` event names instead. + `RealtimeConfigSchema`. - `node_modules/@objectstack/spec/src/api/websocket.zod.ts` — the WebSocket - message protocol: subscribe/unsubscribe messages, event delivery - (`filters` not enforced), presence, cursor and collaborative-edit - messages, and ack/error/ping/pong frames. + message protocol: subscribe/unsubscribe messages, event delivery, presence, + cursor and collaborative-edit messages, and ack/error/ping/pong frames. --- ## Authentication & Authorization -### Auth Configuration - -There is no nested `auth` block on endpoints. Auth is declared with flat -fields on the endpoint itself: +RBAC (`definePermissionSet`) and RLS are objectstack-data's surface, not this +one; this section is the endpoint's own auth keys. -```typescript -// RestApiEndpointSchema (plugin-rest-api) endpoints: -{ - public: false, // false (the default) = auth required - permissions: ['admin'], // required permissions - rateLimit: 'default', // named rate-limit policy (a string reference) -} -``` +### Auth Configuration -Declarative `ApiEndpointSchema` endpoints (and the dispatcher) instead use -`authRequired: boolean` (default `true`) — and setting it to `false` obliges you -to arm a `rateLimit` (ADR-0121 D6 — see **Declarative Endpoints → `authRequired` -and the D6 pairing** above). Rate-limit policies themselves are shaped by -`RateLimitConfigSchema`: +There is no nested `auth` block on an endpoint, and no `name` / `request` / +`response` field: auth is the flat `public` + `permissions` pair on +`RestApiEndpointSchema`, and declarative `ApiEndpointSchema` endpoints (and the +dispatcher) use `authRequired: boolean` (default `true`) — setting it `false` +obliges you to arm a `rateLimit` (ADR-0121 D6 — see **Declarative Endpoints → +`authRequired` and the D6 pairing** above). Rate-limit policies themselves are +shaped by `RateLimitConfigSchema`: ```typescript @@ -454,98 +340,36 @@ Provider and login contracts live in `username`, `password`, `provider`, and `redirectTo`. Read that file for the session and token response shapes before wiring an auth flow. -### Security Layers - -| Layer | Scope | Description | -|:------|:------|:------------| -| **Authentication** | Request | Who is the caller? (JWT, API key, OAuth) | -| **RBAC** | Object | Role-based access control (profile → permissions) | -| **RLS** | Record | Row-level security (visibility rules per record) | -| **FLS** | Field | Field-level security (hide/mask sensitive fields) | - -> **Key rule:** RBAC controls what objects/operations a user can access. -> RLS controls which records within those objects are visible. FLS controls -> which fields are readable/writable. - --- -## Datasource Configuration - -Connect to external data sources for virtualised data access. -`DatasourceSchema` has no `type`, `connection`, or `readOnly` fields — the -connection settings live in the driver-specific `config` record, and -read-only safety comes from `schemaMode` plus the `external` write gate: - - -```typescript -import { defineDatasource } from '@objectstack/spec'; - -export const legacyErp = defineDatasource({ - name: 'legacy_erp', - driver: 'postgres', - config: { - host: 'erp.internal.example.com', - port: 5432, - database: 'erp_production', - }, - ssl: { enabled: true }, - schemaMode: 'external', // DDL forbidden; schema mismatch fails boot - external: { allowWrites: false }, // required when schemaMode != 'managed' -}); -``` - -### Supported Drivers - -Registered driver ids in the datasource driver catalog: +## Datasources -| Driver | Use Case | -|:-------|:---------| -| `postgres` | Primary production database | -| `mysql` | Legacy systems, WordPress integration | -| `mongodb` | Document store (`mongo` is a legacy alias) | -| `sqlite` | Local development, embedded apps | -| `sqlite-wasm` | Browser / edge SQLite (WASM) | -| `turso` | Edge SQLite (`@objectstack/driver-turso`) | -| `memory` | Unit tests, development | +`defineDatasource`, `schemaMode`, the `external` write gate and +`credentialsRef` are objectstack-data → `rules/datasources.md`; driver packages +and the Turso Cloud/EE caveat are objectstack-platform → **Driver Selection +Guide**; the `driver` id vocabulary is `data/driver/config-registry.zod.ts`. --- -## Inter-Service Communication +## Error Envelopes & the Code Ledger (ADR-0112) -### Service Contracts +`error.code` is **two-tier**: the closed `StandardErrorCode` catalog plus the +codes registered per owning package in `api/error-code-ledger.zod.ts`. +`ApiErrorSchema.code` validates the union, so an **unregistered code fails +parse → fails the envelope conformance suites → fails CI**. -ObjectStack uses typed service contracts defined in `@objectstack/spec/contracts`. -The data contract is `IDataEngine` (`find(objectName, query?: EngineQueryOptions)`, -`findOne`, `insert`, `update`, `delete`, `count`, `aggregate`, plus optional -`vectorFind`/`execute`) — there is no `DataService` contract, and no `batch`. - -### Kernel Service Resolution - -Services are resolved through the microkernel with `kernel.getService(name)` -(there is no `kernel.resolve()`); an async variant `kernel.getServiceAsync` -supports factory-created services: - - -```typescript -import type { IDataEngine } from '@objectstack/spec/contracts'; - -declare const kernel: { getService(name: string): T }; - -async function firstTenAccounts() { - const data = kernel.getService('data'); - return data.find('account', { limit: 10 }); -} -``` +That ledger takes **framework packages only**. A downstream repo keeps its OWN +ledger and composes the two checks itself — `envelopeViolations(body)` for the +SHAPE, `makeApiErrorSchema(yourCodes)` for the VOCABULARY (both in +`api/contract.zod.ts`). What no repo may do is emit a code in no ledger at all. --- ## Best Practices -1. **Version your APIs** — always use `/api/v1/` prefix. Breaking changes get - a new version (`v2`). -2. **Use auto-generated APIs** whenever possible. Only create custom endpoints +1. **Use auto-generated APIs** whenever possible. Only create custom endpoints for business logic that cannot be expressed through CRUD + triggers. -3. **Return consistent error shapes.** The dispatcher envelope is +2. **Return consistent error shapes.** The dispatcher envelope is `DispatcherErrorResponseSchema`: `{ success: false, error: { code, message, httpStatus?, route?, service?, hint? } }`, where `code` is the **semantic** string and `code`/`message` are required. General API errors use @@ -553,28 +377,25 @@ async function firstTenAccounts() { return flat `{ error, code }` bodies instead (e.g. `CONCURRENT_UPDATE` → 409, `VALIDATION_FAILED` → 400) — do not assume every error arrives in the `success: false` envelope. -4. **Document every endpoint** with `description` and response schemas. -5. **Apply least-privilege auth.** Every endpoint should declare its required +3. **Apply least-privilege auth.** Every endpoint should declare its required permissions explicitly. -6. **Design idempotent writes deliberately.** `upsert` is DERIVED (`create` ∧ - `update`), not an `apiMethods` value, and `@objectstack/rest` generates no - upsert route today. External integrations query by a unique external ID and - branch to create or update (the per-object - `POST /api/v1/data/{object}/batch` endpoint can group those writes). +4. **Design idempotent writes deliberately.** No upsert route is generated, so + an external integration queries by its unique external ID and branches to + create or update — group those writes through + `POST /api/v1/data/{object}/batch`. --- ## Common Pitfalls -1. **Exposing internal fields via API.** Use FLS (field-level security) or - explicit `apiMethods` to restrict what is visible. -2. **Missing pagination.** Always paginate list endpoints. Default page size - should be 20–50, with a max of 200. -3. **Not handling 409 Conflict.** Concurrent updates should use optimistic - locking (version field) and return `409` on conflict. -4. **Ignoring rate limiting.** Always configure rate limits for public and - external-facing APIs. -5. **Assuming `DELETE` is recoverable.** ObjectStack `DELETE` is a hard +1. **Not handling 409 Conflict.** The generated `PATCH /api/v1/data/{object}/:id` + route does optimistic concurrency with two spellings: the `If-Match` header + **or** an `expectedVersion` field in the JSON body — **the body wins** when + both are sent, and the token is typically the `updated_at` value the client + read. Sending neither skips the check; a mismatch answers **409 + `CONCURRENT_UPDATE`**; the quoted-empty entity-tag (`""`) is refused **400 + `VALIDATION_FAILED`**, not treated as omitted. +2. **Assuming `DELETE` is recoverable.** ObjectStack `DELETE` is a hard delete — there is no recycle bin (the dead `enable.trash` flag was removed in 16.x). For recoverability, use per-field `trackHistory` (audit trail) or a `lifecycle` archive policy instead of custom soft-delete logic. From 19f26f474c15c4d92715afde0f85f760ba6256fa Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 2 Sep 2026 06:46:34 +0000 Subject: [PATCH 2/3] docs(skills): regenerate skills index after the objectstack-api frontmatter edit Generator output only: `pnpm --filter @objectstack/spec gen:skill-docs` writes both files from the SKILL.md frontmatter, and `check:skill-docs` fails without this. The only change is API-A-02 dropping "versioning" from the description. Part of #14304 Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1 --- content/docs/ai/skills-reference.mdx | 4 ++-- skills/README.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/content/docs/ai/skills-reference.mdx b/content/docs/ai/skills-reference.mdx index 657b24c9bb..145b0c2b66 100644 --- a/content/docs/ai/skills-reference.mdx +++ b/content/docs/ai/skills-reference.mdx @@ -49,7 +49,7 @@ ObjectStack ships **11 skills** — one per authoring domain, plus process skill | 4 | [UI](#ui) | `ui` | `skills/objectstack-ui/` | Author ObjectStack UI metadata — Views (list/form/kanban/calendar/gantt), Apps (navigation), Pages (structured plus the HTML and React source-authoring tiers, ADR-0080/0081), Dashboards, Reports, Charts, Actions, and package Docs (`src/docs/*.md`). | | 5 | [Automation](#automation) | `automation` | `skills/objectstack-automation/` | Design ObjectStack automation — Flows (visual logic), Triggers, Approvals, state machines, scheduled jobs, and webhooks. | | 6 | [AI](#ai) | `ai` | `skills/objectstack-ai/` | Design ObjectStack AI skills, tools, knowledge sources, conversations, model registry entries, and MCP integrations. | -| 7 | [API](#api) | `api` | `skills/objectstack-api/` | Design the server-side API surface that an ObjectStack runtime exposes — REST endpoints, auth providers, realtime channels, error envelopes, batch/versioning contracts. | +| 7 | [API](#api) | `api` | `skills/objectstack-api/` | Design the server-side API surface that an ObjectStack runtime exposes — REST endpoints, auth providers, realtime channels, error envelopes, batch contracts. | | 8 | [i18n](#i18n) | `i18n` | `skills/objectstack-i18n/` | Author ObjectStack translation bundles — object/field labels, view text, app navigation strings, automation messages — and configure locale fallback, coverage reporting, and the per-locale source layout. | | 9 | [Formula](#formula) | `expression` | `skills/objectstack-formula/` | Author CEL expressions used across ObjectStack — formula fields, field conditional rules (`visibleWhen`, `readonlyWhen`, `requiredWhen`), validation / sharing / visibility predicates, flow conditions, and dynamic seed values. | | 10 | [PM Dispatch](#pm-dispatch) | `process` | `skills/objectstack-pm-dispatch/` | Run a project-manager dispatch loop over a GitHub backlog: triage and queue ready issues, claim each one, dispatch it to a parallel developer agent that returns a structured JSON report, review the results against GitHub, and drive accepted pull requests to landing — escalating to the maintainer only what genuinely needs a human decision. Ships the developer-agent operating template the loop injects into every dispatch (no custom agent types required) and the upstream-reporting procedure for platform defects an app project finds. | @@ -145,7 +145,7 @@ Do not use for general LLM prompting questions unrelated to ObjectStack metadata **Domain** `api` · **Path** `skills/objectstack-api/` -Design the server-side API surface that an ObjectStack runtime exposes — REST endpoints, auth providers, realtime channels, error envelopes, batch/versioning contracts. +Design the server-side API surface that an ObjectStack runtime exposes — REST endpoints, auth providers, realtime channels, error envelopes, batch contracts. Use when the user is adding `*.endpoint.ts`, configuring auth providers, defining custom routes, or extending the REST generator. diff --git a/skills/README.md b/skills/README.md index eba5382b3b..1185e4d402 100644 --- a/skills/README.md +++ b/skills/README.md @@ -35,7 +35,7 @@ apps too). | [UI](./objectstack-ui/SKILL.md) | `ui` | Author ObjectStack UI metadata — Views (list/form/kanban/calendar/gantt), Apps (navigation), Pages (structured plus the HTML and React source-authoring tiers, ADR-0080/0081), Dashboards, Reports, Charts, Actions, and package Docs (`src/docs/*.md`). | | [Automation](./objectstack-automation/SKILL.md) | `automation` | Design ObjectStack automation — Flows (visual logic), Triggers, Approvals, state machines, scheduled jobs, and webhooks. | | [AI](./objectstack-ai/SKILL.md) | `ai` | Design ObjectStack AI skills, tools, knowledge sources, conversations, model registry entries, and MCP integrations. | -| [API](./objectstack-api/SKILL.md) | `api` | Design the server-side API surface that an ObjectStack runtime exposes — REST endpoints, auth providers, realtime channels, error envelopes, batch/versioning contracts. | +| [API](./objectstack-api/SKILL.md) | `api` | Design the server-side API surface that an ObjectStack runtime exposes — REST endpoints, auth providers, realtime channels, error envelopes, batch contracts. | | [i18n](./objectstack-i18n/SKILL.md) | `i18n` | Author ObjectStack translation bundles — object/field labels, view text, app navigation strings, automation messages — and configure locale fallback, coverage reporting, and the per-locale source layout. | | [Formula](./objectstack-formula/SKILL.md) | `expression` | Author CEL expressions used across ObjectStack — formula fields, field conditional rules (`visibleWhen`, `readonlyWhen`, `requiredWhen`), validation / sharing / visibility predicates, flow conditions, and dynamic seed values. | | [PM Dispatch](./objectstack-pm-dispatch/SKILL.md) | `process` | Run a project-manager dispatch loop over a GitHub backlog: triage and queue ready issues, claim each one, dispatch it to a parallel developer agent that returns a structured JSON report, review the results against GitHub, and drive accepted pull requests to landing — escalating to the maintainer only what genuinely needs a human decision. Ships the developer-agent operating template the loop injects into every dispatch (no custom agent types required) and the upstream-reporting procedure for platform defects an app project finds. | From 19ee58537ba14cf9dc8afa5286053dc62013bfcc Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 2 Sep 2026 06:50:45 +0000 Subject: [PATCH 3/3] chore(gates): ratchet the role-word baseline down after API-C-03 `check:role-word` red on this branch with the ratchet-DOWN remedy: the file's one baselined occurrence of the reserved word was the Security Layers row `| **RBAC** | Object | Role-based access control (profile -> permissions) |`, deleted by API-C-03. The gate names `--update` as the author's own path when a baselined file goes clean, so the entry is dropped rather than left as budget a later edit could spend silently. Baseline 43 -> 42 files; the objectstack-api row is gone, no other row moves. Part of #14304 Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1 --- scripts/role-word-baseline.json | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/role-word-baseline.json b/scripts/role-word-baseline.json index 119885f46c..7fda3150d7 100644 --- a/scripts/role-word-baseline.json +++ b/scripts/role-word-baseline.json @@ -34,7 +34,6 @@ "content/docs/releases/v17.mdx": 2, "content/docs/ui/forms.mdx": 3, "skills/objectstack-ai/SKILL.md": 5, - "skills/objectstack-api/SKILL.md": 1, "skills/objectstack-automation/SKILL.md": 1, "skills/objectstack-data/SKILL.md": 4, "skills/objectstack-data/rules/relationships.md": 1,