Skip to content

Commit af1d59b

Browse files
docs(skills): objectstack-api teaches the /meta/:type list's ?names= filter and its closed query set (#30844)
<!-- claude-projects-attribution: {"github_login":"os-tesla"} --> _Requested by **Tesla** · [project thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6Jao?thread=cmsg_01AXsQkLFKRdQCqQ3RTr6JaoNTm3wqJ8tNxUcJRy6fYa9g)_ domain:skills#2 · os-tesla · session_01L9qiby5ktwhsqg839UYDQs Fixes #30710 Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:`skills/objectstack-api/SKILL.md` 的 Metadata API 一节补一段:`GET /api/v1/meta/:type` 列表支持 `?names=a,b,c` 一次按名字读多条(一个逗号分隔值;没有的名字直接缺席;空值返回 400;`package` 列表拒绝该参数),并且列表只认七个查询参数(`package`、`preview`、`object`、`include`、`id`、`names`、`locale`),其它参数一律 400 `VALIDATION_ERROR`,不再静默忽略。评测新增第 7 例(三个对象定义合成一次请求),changeset 一条。 - 为什么改:这是 #29236 裁决 A 的技能文档半边,服务端已在 PR #30709 落地。技能不写,读技能的客户 agent 仍会按名字逐个发请求,拼错的过滤参数也不知道会被拒。 - 风险与回滚:只改技能文本与评测夹具,运行时零改动;行文全部对照 `main` 上的代码与测试写成。回滚即 revert。 - 先看:`skills/objectstack-api/SKILL.md` 的 Metadata API 段(新段落);同文件三处删减(`/data` 前缀句、Datasources 小节折入 Skill Boundaries 表、RBAC 旁注去掉复述的符号);`evals/endpoints-auth-routes.json` 第 7 例。 - 两分钟验证:`node scripts/check-skills-token-ratchet.mjs`,预期 `skills/objectstack-api/SKILL.md is 5141 tokens (ceiling 6314)`,上限行未动;再看 `packages/rest/src/meta-item-read-gate.ts` 的 `META_LIST_QUERY_PARAMS` 七个键与技能段落一致。 - 需要你做的:本 PR 触 `skills/**`(Tier H),需要受管审批人在 GitHub 上 Approve 一次;之后席位落地。 ## What changes `skills/objectstack-api/SKILL.md`, section "Metadata API (`/meta`)": the two-line "read-side query params are client contracts" paragraph becomes one paragraph that states what the list door serves on `main` since PR #30709: - the closed query set `package`, `preview` (`draft`), `object`, `include` (`content`), `id`, `names`, `locale` (`META_LIST_QUERY_PARAMS` in `packages/rest/src/meta-item-read-gate.ts`), any other key refused `400 VALIDATION_ERROR` instead of ignored; - `?names=a,b,c`, one comma-separated value, narrowing the list to the named items the caller may see (chain step 2a runs after the per-caller gate); an unknown name simply absent; an empty value refused `400` (`metaListNames`); the `package` list refusing the key (`NAMES_ON_PACKAGE_LIST_MESSAGE`, its items are keyed by id); - the closing sentence that these are client contracts, nothing a stack declares, kept. Not taught, on purpose: the repeated-key arity refusal, the located message texts, the `?id=` / `?object=` per-type meaning and the runtime dispatcher twin. The skill names the keys and the one behaviour an author or caller must know; the rest is the route's own text. **Growth paid in the same file, by deletion (no re-wrap):** | Deleted | Why it was restatement | |:--|:--| | "Data CRUD lives under the `/data` prefix." and "aggregation goes through `POST /api/v1/data/{object}/query` with `groupBy`/`aggregations` in the body" | The route block right above shows every `/data` path, and the derived-operations table's `aggregate` row carries the same `query` + `groupBy`/`aggregations` spelling. The negative pins (no `/bulk`, no `GET .../aggregate`) stay. | | The `## Datasources` section | Pure routing: every pointer in it has its home in objectstack-data (`rules/datasources.md`) and objectstack-platform (Driver Selection Guide, which carries the Turso Cloud/EE caveat). The Skill Boundaries table already routed datasources there; it now names the rule file and gains a `Pick a driver package` row, so no pointer is lost. | | `(`definePermissionSet`)` and ", not this one" in the auth aside | The Skill Boundaries table routes permissions to objectstack-data; the symbol is that skill's to teach. | `skills/objectstack-api/evals/endpoints-auth-routes.json`: case 7, "three object definitions, one request": expects one `GET /api/v1/meta/object?names=account,contact,lead`, must contain `?names=` / `/api/v1/meta/object` / `VALIDATION_ERROR`, must not contain `?name=`, `?ids=`, `?filter=`. Added because case 6 already evaluates a request-shape answer (the import body), so a metadata-read case is inside the fixture's convention. The seat may drop it if the convention is read narrower. `.changeset/30710-meta-list-names-filter.md`: `'@objectstack/skills': patch`, both declaration lines. **Readings (os-dev rule for `skills/**`):** | Reading | Before (`origin/main` `b6182249`) | After (`a2f2be9c`) | |:--|:--|:--| | `skills/objectstack-api/SKILL.md` lines | 446 | 441 | | `skills/objectstack-api/SKILL.md` tokens (`check-skills-token-ratchet`) | 5144 | 5141 (ceiling 6314, unchanged) | | Package, all 11 `skills/*/SKILL.md`, lines | 4523 | 4518 | | Package, all 11 `skills/*/SKILL.md`, tokens (gate formula) | 56716 | 56713 | No ceiling row moves. The eval fixture is measured, not ratcheted (1234 → 1484 tokens). ## Why #30710, a ruling's owed skills follow-up (#29236, ruling 6580261740 letter A): PR #30709 serves `?names=` and closes the list's query set, and its body declares `Skills-docs: follow-up #30710`. Until the skill says so, an agent reading it sends one request per name and cannot learn that a misspelt filter is refused rather than ignored. ## Tests No runtime code changes. The teaching text was read off `main`'s `META_LIST_QUERY_PARAMS`, `metaListNames`, `metaListUnknownParamRefusal` and the pins in `packages/rest/src/meta-query-arity-doors.test.ts` and `meta-list-names-filter.test.ts` at `c17719dd`; nothing is stated that those do not pin. ## Acceptance notes - `skills/objectstack-api/evals/` has no runner script in this repo; the fixture is measured by `check-skills-token-ratchet` (population 1b) and parsed as JSON. Case 7 follows the existing shape (`id`, `prompt`, `expected_output`, `files`, `assertions`). - Overlap check: open PRs touching `skills/objectstack-api/**` at dispatch time: none (#30821 and #30764 touch objectstack-platform only; #30805 declares `skills/**` wide but has no PR yet). `origin/main` was merged at `b6182249` before the final push; no conflict. - `OS_SKIP_DTS=1` on a whole-tree build reds `check:dual-build-cjs-loads` on every package's `types` condition and the `ui/` packages' `tsc`; the reading below comes from a declarations build of `./packages/**`. ## Verification Derived with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` against the three-file diff. Run twice, every line with stdin closed, exit codes landed first: - At `36b5c9e6` (before the `origin/main` merge): 44 derived families, 44 run, 44 exit 0; `--ran` reconciliation: 0 NOT-MEASURED, 0 UNRUN. - At `a2f2be9c` (the head, after merging `origin/main` `b6182249`): 45 derived families (the merge adds `scripts/ci/snapshot-next.mjs --self-test`), 45 run, 45 exit 0; `--ran` reconciliation: 0 NOT-MEASURED, 0 UNRUN. Ran lists: `skills-seat-2/out/30710-gates-ran-{36b5c9e,a2f2be9c}.list` in the project files. <details><summary>Gate readings at a2f2be9</summary> - `check-skills-token-ratchet`: `skills/objectstack-api/SKILL.md is 5141 tokens (ceiling 6314; headroom 1173)`, exit 0. - `check:skill-identifier-liveness`: `Leg 1: 486 citation(s) over 71 published file(s) … Leg 2: 8 registered exhaustive section(s), 0 ledgered gap(s)`, exit 0. - `check:dual-build-cjs-loads`: `116 published require entry point(s) across 71 package(s) load; 2456 emitted CommonJS file(s) parse`, exit 0 (declarations build of `./packages/**`). - `check:lean-entry-closure`, `pnpm --filter @objectstack/spec run check:skill-docs`, `check:skill-compatibility`, `check:skill-frame-sync`, `check:pm-skill-id-lint`, `check-changeset-no-major --base origin/main`, `check-empty-changeset --base origin/main`, `check-adr-0087-registration --base origin/main`, `check:pm-governed-merges`, `check:doc-authoring`, `check:nul-bytes`, `check:role-word`, the rest: exit 0. - Tier: `dispatch-gates --tier` on the three paths: `skills/**` clause ①, `claude-fable-5-1` mandated, no one-line exit; this thread runs on it. `check-governed-merges --test skills/objectstack-api/SKILL.md`: Tier H. </details> CI families (`Lint & Repo Gates`, `Test Core`, the rest) are the PR's check-runs, not re-run locally. ## Human Minimum - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. - [x] 2. Every name I added uses the casing for its kind. - [x] 3. No formatter ran; `pnpm lint` is CI's run (no source file in the diff). - [x] 4. One issue, one branch off an up-to-date `main`, one pull request. - [x] 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset. - [x] 6. `packages/spec` not touched. - [x] 7. No hand edits to `content/docs/references/`, `packages/*/CHANGELOG.md` or `content/docs/releases/`. - [x] 8. Title, body and comments are in English. - [x] The *Before Submitting* list in CONTRIBUTING.md is done. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01L9qiby5ktwhsqg839UYDQs --------- Co-authored-by: objectstack-fleet[bot] <332303061+objectstack-fleet[bot]@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com>
1 parent d0ebb8f commit af1d59b

3 files changed

Lines changed: 33 additions & 20 deletions

File tree

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@objectstack/skills': patch
3+
---
4+
5+
docs(skills): objectstack-api's Metadata API section teaches the `/meta/:type` list door's `?names=a,b,c` filter (one comma-separated value; an unknown name absent, an empty value refused `400`, the `package` list refuses the key) and its closed query set (`package`, `preview`, `object`, `include`, `id`, `names`, `locale`; any other key `400 VALIDATION_ERROR`)
6+
7+
Clause-②: no
8+
Metadata-protocol: none

‎skills/objectstack-api/SKILL.md‎

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ metadata:
2525

2626
| Need | Use instead |
2727
|:-----|:------------|
28-
| Model objects, fields, permissions, or datasources | **objectstack-data** |
28+
| Model objects, fields, permissions, or datasources (`rules/datasources.md`) | **objectstack-data** |
29+
| Pick a driver package | **objectstack-platform** → Driver Selection Guide |
2930
| Filter, sort, paginate, or aggregate a request | **objectstack-query** |
3031
| Register a kernel service, or read one from a plugin | **objectstack-platform** |
3132
| CEL in a route guard or auth predicate | **objectstack-formula** |
@@ -50,10 +51,8 @@ POST /api/v1/data/{object}/batch # Per-object batch operations
5051
POST /api/v1/batch # Cross-object atomic batch
5152
```
5253

53-
Data CRUD lives under the `/data` prefix. There is no `/bulk` route and no
54-
`GET .../aggregate` route — batch writes go through the `batch` endpoints, and
55-
aggregation goes through `POST /api/v1/data/{object}/query` with
56-
`groupBy`/`aggregations` in the body.
54+
There is no `/bulk` route and no `GET .../aggregate` route — batch writes go
55+
through the `batch` endpoints, aggregation through the `query` one.
5756

5857
> **Key rule:** If your object defines `apiMethods`, only those operations (and
5958
> what derives from them) are exposed; `[]` means deny-all. The authorable
@@ -69,8 +68,13 @@ GET /api/v1/meta/:type # List metadata items of a type (object, view,
6968
GET /api/v1/meta/:type/:name # Read a single metadata item
7069
```
7170

72-
The read-side query params (`?preview=draft`, `?package=`, `?include=content`)
73-
are **client** contracts — nothing in a stack declares them.
71+
The list reads a closed query set — `package`, `preview` (`draft`), `object`,
72+
`include` (`content`, `grants`), `id`, `names`, `locale` — and refuses any other key with
73+
`400 VALIDATION_ERROR` instead of ignoring it. `?names=a,b,c` (one
74+
comma-separated value) narrows the list to the named items the caller may see:
75+
an unknown name is simply absent, an empty value is refused `400`, and the
76+
`package` list (keyed by id) refuses the key. These are **client** contracts —
77+
nothing in a stack declares them.
7478

7579
### Public (anonymous) Form Endpoints
7680

@@ -315,8 +319,8 @@ Realtime contracts are pointer-style — read the spec source for exact shapes:
315319

316320
## Authentication & Authorization
317321

318-
RBAC (`definePermissionSet`) and RLS are objectstack-data's surface, not this
319-
one; this section is the endpoint's own auth keys.
322+
RBAC and RLS are objectstack-data's surface; this section is the endpoint's
323+
own auth keys.
320324

321325
### Auth Configuration
322326

@@ -350,15 +354,6 @@ session and token response shapes before wiring an auth flow.
350354

351355
---
352356

353-
## Datasources
354-
355-
`defineDatasource`, `schemaMode`, the `external` write gate and
356-
`credentialsRef` are objectstack-data → `rules/datasources.md`; driver packages
357-
and the Turso Cloud/EE caveat are objectstack-platform → **Driver Selection
358-
Guide**; the `driver` id vocabulary is `data/driver/config-registry.zod.ts`.
359-
360-
---
361-
362357
## Error Envelopes & the Code Ledger (ADR-0112)
363358

364359
`error.code` is **two-tier**: the closed `StandardErrorCode` catalog plus the
@@ -414,8 +409,8 @@ keys mean the run produced none — never invent an empty.
414409
`CONCURRENT_UPDATE`**; the quoted-empty entity-tag (`""`) is refused **400
415410
`VALIDATION_FAILED`**, not treated as omitted.
416411
2. **Assuming `DELETE` is recoverable.** ObjectStack `DELETE` is a hard
417-
delete — there is no recycle bin (the dead `enable.trash` flag was removed
418-
in 16.x). For recoverability, use per-field `trackHistory` (audit
412+
delete — there is no recycle bin.
413+
For recoverability, use per-field `trackHistory` (audit
419414
trail) or a `lifecycle` archive policy instead of custom soft-delete logic.
420415

421416
---

‎skills/objectstack-api/evals/endpoints-auth-routes.json‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,16 @@
6060
"must_contain": ["applyOn", "'create'", "sourceField", "targetField"],
6161
"must_not_contain": ["writeMode: 'insert'"]
6262
}
63+
},
64+
{
65+
"id": 7,
66+
"prompt": "Our integration boots by reading the `account`, `contact` and `lead` object definitions from the runtime's metadata API. Today it sends three requests; our `?name=account,contact,lead` attempt came back 400. Make it one request.",
67+
"expected_output": "One `GET /api/v1/meta/object?names=account,contact,lead` — the list door's `?names=` filter takes one comma-separated value and answers only the named items the caller may see; a name that matches no item is simply absent (no 404), and an empty value is refused `400`. Does not spell the filter `?name=`, `?ids=` or `?filter=`: the list reads a closed query set (`package`, `preview`, `object`, `include`, `id`, `names`, `locale`) and refuses any other key with `400 VALIDATION_ERROR` instead of ignoring it, so a misspelt filter never silently answers the whole list.",
68+
"files": [],
69+
"assertions": {
70+
"must_contain": ["?names=", "/api/v1/meta/object", "VALIDATION_ERROR"],
71+
"must_not_contain": ["?ids=", "?filter="]
72+
}
6373
}
6474
]
6575
}

0 commit comments

Comments
 (0)