Skip to content

Commit 620b6e6

Browse files
docs(skills): stop teaching objectui as a separate repo, list it in the catalog index, repair dead anchors, author sharingModel in object examples (#31152)
<!-- claude-projects-attribution: {"github_login":"huangyiirene"} --> _Requested by **Irene** · [project thread](https://claude.ai/code/project/chan_01TJXtQ1tVD26ecLDZxFS67x?thread=cmsg_01TJXtQ1tVD26ecLDZxFS67x8SGTdYN8vqh1R82GjyqZn5)_ Fixes #31114 Clause-②: no Metadata-protocol: none `dispatch-gates --tier` reads a MANDATORY model tier for `skills/**` (clause ① of the model-tiering ruling). The maintainer's standing order in this project (2026-11-03) waives it, and this card ran on the project's default model. The dispatching seat runs the contract review. ## 维护者速读 - 改了什么:`objectui` 技能 README、`page-builder.md` 和目录 README 不再把 ObjectUI 说成独立仓库,改指本仓库 `ui/` 与目录 README 的固定版本 `experimental_sync` 安装;`build-skill-docs.ts` 按“有 `SKILL.md` 且非 `metadata.internal`”收录技能,生成的索引与文档页补上 `objectui`;修三处死锚点;`bootstrap.md` 版本改为 `18.3.0`;`relationships.md`、`lifecycle.md`、`datasources.md` 的完整对象示例补 `sharingModel`;`indexing.md` 的租户列改为注入的 `organization_id`;`objectstack-query` 补 `$empty`;升级技能注明 `objectstack-pm-dispatch` 已不在 18 目录中。 - 为什么改:UI 合入本仓库和几次“原样搬移”重构后,这些地方仍在教过时或会被拒的写法(#31114):缺 `sharingModel` 的对象发布会被 `security-owd-unset` 拒绝;索引写未声明的 `tenant_id` 会被字段引用检查拒绝。 - 风险与回滚:只改技能文字、eval、一个生成脚本及其生成产物;所有文件都在 token 上限内,未上调任何上限:七个文件(`objectui/README.md`、`relationships.md`、`indexing.md`、`plugin-lifecycle.md`、`service-registry.md`、`objectstack-query/SKILL.md`、`objectstack-upgrade/SKILL.md`)的增长落在既有余量内,其余靠同文件删除重复内容支付,见下文;发 `@objectstack/skills` patch;回滚即 revert。 - 先看:`packages/spec/scripts/build-skill-docs.ts` 的收录谓词,和 `skills/objectstack-data/rules/relationships.md` 的 `sharingModel` 示例。 - 两分钟验证:`node scripts/check-skills-token-ratchet.mjs` 与 `pnpm --filter @objectstack/spec check:skill-docs`,预期都退出 0。 - 需要你做的:`skills/**` 属 Tier H(人合),需要 `os-zhuang` 或 `hotlong` 的一次 approval,之后席位落地。 ## What changes Every item was re-read on `main` before editing (the card was measured on `aa3e21cc`; this branch is on `9f61e06e`). ### 1. ObjectUI is not a separate repository Code site: the UI lives at `ui/` in this repository since #25752, and `packages/skills` ships the `objectui` skill since #25832. - `skills/README.md` § Related repositories → § Related code: `ui/` in this repository (link to `tree/main/ui`), in place of the archived `objectstack-ai/objectui` link. - `skills/objectui/README.md`: the `skills.sh` badge for `objectstack-ai/objectui` is removed. The install section now says the skill ships in `@objectstack/skills`, shows the two pinned commands the catalog README teaches (`pnpm add -D "@objectstack/skills@…"` + `npx skills@1.7.2 experimental_sync --agent claude-code --copy -y`), and links the catalog README for other package managers and agents. That link is an absolute `objectstack/blob/main/skills/README.md` URL: `ui/scripts/check-doc-links.mjs` (L732, `diskRoot: '../skills/objectui'`) refuses a relative href that leaves the bundle, because the skill ships to customer projects as one directory. The "CLI auto-detects your agent" sentence is removed, because it is false under `experimental_sync --agent`. The repository link now points at `ui/`. - `skills/objectui/guides/page-builder.md`: the `plugin-gantt` README link now goes to `objectstack/tree/main/ui/packages/plugin-gantt`, a directory page that renders the README. That URL is 3 bytes shorter, so it fits the ceiling. ### 2. The catalog index lists `objectui` Code site: `packages/spec/scripts/build-skill-docs.ts`. The `startsWith('objectstack-')` filter is replaced by "carries a `SKILL.md` and is not `metadata.internal`". The predicate is a copy of `isMarkedInternal` in `packages/create-objectstack/src/template-consistency.test.ts` L608, spelled the same way. `DISPLAY` gains `{ name: 'objectui', label: 'ObjectUI' }`. Two parser changes were needed: - `objectui`'s frontmatter `description:` is a plain one-line scalar, not a folded `>` block, and the parser read only the folded block. - `objectui` spells its clauses "Use for" / "Do NOT use". The split now matches `Use (when|for)` and is case-insensitive on `Do not use`. The first match wins, so the ten existing entries render byte-identically. The only change in the regenerated output is the added row and card, plus the count (10 → 11). Regenerated with `pnpm --filter @objectstack/spec gen:skill-docs` and committed as generated: the index block of `skills/README.md` and `content/docs/ai/skills-reference.mdx`. The `DECLARED_WATCH_HINTS` population is unchanged: `skills/*/SKILL.md` already covered `objectui/SKILL.md`. Cross-seat note: `packages/spec/scripts` belongs to `domain:spec`, and per the triage it rides along here with no spec seat online. `@objectstack/spec` does not publish `scripts/` (its `files` list), so the changeset is `@objectstack/skills` only. ### 3. Three dead anchors - `skills/objectstack-data/references/data-hooks.md` L961: the "Project hooks pattern" line pointed at `../SKILL.md#lifecycle-hooks`. The data `SKILL.md` has no hooks section any more, and its Quick Reference row (L46) routes to this very file. The line is deleted (seat ruling). The fix is the deletion; the home is this file. - `skills/objectstack-platform/rules/plugin-lifecycle.md` L259 now points at `../references/plugin-development.md#complete-plugin-example`. That section (L164) is the AuditPlugin example the sentence describes: a service registered in `init()`, `kernel:ready` / `metadata:reloaded` hooks, cleanup in `destroy()`. - `skills/objectstack-platform/rules/service-registry.md` L147-149 now points at `../references/plugin-development.md#well-known-plugin-names--services`, and the hand-written "13 rows" is dropped per #27522. **Card premise corrected:** the card said that heading exists nowhere. It exists at `references/plugin-development.md` L280, with 12 rows, not 13. ### 4. `bootstrap.md` pins the current version `skills/objectstack-platform/references/bootstrap.md`: all five pins in the starting `package.json` (L147-153, not only the two the card named) are now `18.3.0`. That is the version in `packages/skills/package.json`, `packages/spec`, `packages/runtime`, `packages/cli`, `plugin-hono-server`, and every SKILL.md `metadata.version`. The edit is the same byte length. ### 5. Complete object examples author `sharingModel` Code site: `packages/lint/src/validate-security-posture.ts` L843-877. A non-system object with no `sharingModel` is `security-owd-unset` (error). The object counts as system when `isSystemObject` (L280) says so: `isSystem: true` or a `sys_` name. - `rules/relationships.md`: `sharingModel: 'private'` on `account`, `contact`, `invoice`, `project`, `employee`, `project_assignment` and `category`. `sharingModel: 'controlled_by_parent'` on the two `master_detail` children, `invoice_line_item` and `invoice_line` (the lint hint's prescription for master-detail children, consistent with the file's § Owner edits and its Relationship Types "Sharing: Inherits parent"). The ❌ counter-example (`invoice_line_item` with a `lookup`) is a complete create call, and it stays as it is: it is kept as a labelled counter-example under § Incorrect vs Correct, a shape the page tells the reader not to write. - `rules/lifecycle.md`: `sharingModel: 'private'` on `api_call_log`, `import_ticket` and `consent_log`. `sys_automation_run` is a system object (`sys_` prefix, `isSystemObject`), so it is exempt and unchanged. - `rules/datasources.md`: `sharingModel: 'public_read'` on `ext_customer`. The binding has no `external.writable: true`, so it is read-only (`validate-external-owner-sharing.ts` L190), and the lint's prescription for an unprovable owner anchor is `READ_ONLY_EXTERNAL_WAY_OUT` (L136): `'public_read'`, because on `'private'` a member reads no row. - **No edit, measured:** `references/bootstrap.md` and `references/plugin-development.md` hold no complete example. Each mentions `ObjectSchema.create({ … })` only in prose, so the card's 1/0 counts are the prose mentions. `bootstrap.md` names the scaffolder's `note.object.ts` as the shape to copy, and that file authors `sharingModel: 'private'` (`packages/create-objectstack/src/templates/blank/src/objects/note.object.ts` L54). ### 6. System columns: what was confirmed - **`examples-objects.md`: card premise wrong, measured, no edit (seat ruling B).** An explicit `created_at` / `created_by` declaration is neither refused nor merely shadowed. `applySystemFields` lets the author's declaration win and forces only `readonly: true` / `system: true` over it (`packages/metadata-core/src/audit-field-governance.ts`, module note L10-15 and `applyAuditFieldGovernance` L121; `label`, `group` and even `type` stay the author's). And `packages/spec/src/data/field-group-layout.ts` L18-21 leaves audit fields out of a grouped layout unless the author explicitly groups them. So `created_at: { …, group: 'system' }` is the supported way to show them in that example's System section. The serial wait on #31127 is moot: the file is untouched. - **`rules/indexing.md` § Multi-Tenant Queries: fixed.** The injected tenant column is `organization_id`, governed by `systemFields.tenant` (`packages/spec/src/data/object.zod.ts` L3309: "the column is always provisioned; the multi-tenant flag governs only its index"). On a multi-tenant host the registry adds the single-column index itself (`packages/objectql/src/registry.ts` `TENANT_SCOPE_INDEX` L1692, `provisionTenantScopeIndex` L1694). An index naming an undeclared `tenant_id` is refused: `indexes[].fields[]` existence is judged against the authored fields plus the injected columns (`packages/lint/src/validate-object-field-refs.ts`, `INDEX_POSITION` L320). The example now indexes `['organization_id', 'status', 'created_at']`, with one paragraph on `systemFields.tenant` and the automatic index. The paragraph scopes the composite to non-unique indexes, because the neighbouring § unique scopes says "do NOT list `organization_id` yourself" for `unique: 'organization'`. ### 7. `plugin-ai` eval: card premise wrong, measured, no edit `skills/objectui/evals/project-setup.json` L20 lists `@object-ui/plugin-ai` under `must_not_contain` (the same at `aa3e21cc`). The fixture already penalises an answer that names the retired package, so there is nothing to replace (seat ruling A). ### 8. `$empty` is taught: confirmed `$empty` is in `FILTER_OPERATORS` (`packages/spec/src/data/filter.zod.ts` L3377), declared on both operator schemas (L1829, L1905), and is the lowering target of `is_empty` / `is_not_empty` (L2760-2763). Its semantics are the ruled per-type table `EMPTY_PREDICATE_DESCRIPTION` (L1796). - `rules/filters.md` Operator Reference: one row, `$empty` → "`IS NULL`, `''` or `[]` by type". - `SKILL.md` § Null & Existence Operators: the row, an example (`false` = has content), and two sentences: the view operators lower to it, and a face that judges by declared type refuses it on a column it holds no declaration for (the built-in `id`), so use `$null` there. - `$regex` / `$options` stay out. ### 9. Upgrade roster `skills/objectstack-upgrade/SKILL.md`: the prose before the `skills remove` block says `objectstack-pm-dispatch` is not in the 18 catalog and that only an older install has it (#17311). The prose carries the note, not the command, so the command stays copy-pasteable. The roster also gains `objectui` (seat review S2): a 17.x project installed it from `objectstack-ai/objectui`, so it has a github-sourced `skills-lock.json` entry that the sync skips, like the `objectstack-*` ones. ## Token readings (`node scripts/check-skills-token-ratchet.mjs`, exit 0) | File | `main` | This branch | Ceiling | |:--|--:|--:|--:| | `skills/objectui/README.md` | 728 | 733 | 735 (inside headroom) | | `skills/objectui/guides/page-builder.md` | 3410 | 3409 | 3411 | | `skills/objectstack-data/references/data-hooks.md` | 10011 | 9987 | 10011 | | `skills/objectstack-data/rules/relationships.md` | 3870 | 3873 | 3873 (inside headroom) | | `skills/objectstack-data/rules/lifecycle.md` | 1590 | 1588 | 1590 | | `skills/objectstack-data/rules/datasources.md` | 908 | 908 | 911 | | `skills/objectstack-data/rules/indexing.md` | 2200 | 2284 | 3241 (inside headroom) | | `skills/objectstack-platform/references/bootstrap.md` | 1350 | 1350 | 1351 | | `skills/objectstack-platform/rules/plugin-lifecycle.md` | 1775 | 1787 | 2408 (inside headroom) | | `skills/objectstack-platform/rules/service-registry.md` | 1754 | 1764 | 2331 (inside headroom) | | `skills/objectstack-query/SKILL.md` | 4234 | 4329 | 5547 (inside headroom) | | `skills/objectstack-query/rules/filters.md` | 2482 | 2477 | 2482 | | `skills/objectstack-upgrade/SKILL.md` | 6160 | 6182 | 6188 (inside headroom) | `skills/README.md` is outside every skill directory and is not ratcheted. No ceiling is raised, so the ruling path was not used. No line was re-wrapped. Every deletion, with the facts it held and where each survives: - **`objectui/README.md`:** the `skills.sh` badge (a pointer to the archived repo's listing; no surviving home needed). The "CLI auto-detects your agent… (e.g. `.claude/skills/objectui/`…) See skills.sh/docs" sentence. Facts: auto-detection is false under the taught `--agent` install; the landing directory and the other agents are in the catalog README (§ install: "the skills land as real files in `.claude/skills/`", "Name your own runtime after `--agent`"), which the new text links. - **`data-hooks.md`:** the dead "Project hooks pattern" reference line (item 3). Its home is this file, which the data `SKILL.md` Quick Reference routes to. - **`relationships.md`:** - § ✅ Correct — Junction Object Pattern: the `post_tag` block is replaced by "The [junction object](#many-to-many-junction-object) pattern above.", mirroring the master_detail Correct entry. Facts: a junction is an explicit object with two `lookup`s, both `required: true`. Home: § Many-to-Many: Junction Object (`project_assignment`, same shape, plus its unique pair index). - The `// Contact can exist without account` comment on `required: false`. Homes: the example's own `// Child: Contact (independent lifecycle)` and the Relationship Types row `lookup | Independent | Optional by default`. - **`lifecycle.md`:** - The intro's "— and, at its end, the save notice a hook hands the saver." Home: § Save Notice, and the data `SKILL.md` Quick Reference row. - `// must: after === maxAge` on the ConsentLog `archive` line. It was the page's only statement of the equality that `object.zod.ts` L1067 enforces at parse, so the follow-up commit restates it at its home (seat review S1). The § Validation Rules counter-example's comment now reads `// ❌ archive.after must equal retention.maxAge`, in place of "❌ Archive window detached from the hot window". - **`datasources.md`:** ", not only on a federated object" in the `columnName` bullet. Home: the same bullet's "on ANY object" and "everywhere". - **`query/rules/filters.md`:** - "; `$null: false` is `IS NOT NULL`" in § Prefer `$null`. Home: the Operator Reference `$null` row (`IS NULL` / `IS NOT NULL`) and `SKILL.md`'s Null table. - The `// Records created in the last 7 days (compute date in application code)` comment. Its first half did not match the code below it (a fixed `new Date('2025-01-01')`), and its second half is the section's lead sentence ("For **ad-hoc queries in application code**, compute the date yourself"). ## Link walk `/mnt/project-files/card-31114/linkwalk.py` (project folder) walks every `skills/**/*.md` outside code fences and inline code. It checks that relative links resolve to a file, that `#anchors` match a GitHub heading slug or an `<a id>` in the target, and that `github.com/objectstack-ai/objectstack/(tree|blob)/main/<path>` links name a path that exists in the checkout. - `main` (`98583cd6`): 243 links, 3 broken (exactly item 3's three). - This branch: 248 links, **0 broken**. - The repo's own `ui/scripts/check-doc-links.mjs` (in `pnpm --dir ui exec vitest run scripts/`, a derived gate) also covers `skills/objectui` and is green. ## Eval cases - `objectstack-data/evals/objects-fields-relationships.json` case 8 (new, item 5): `project` + owned `milestone`. Must contain `ObjectSchema.create`, `sharingModel: 'private'`, `sharingModel: 'controlled_by_parent'`, `master_detail`, `deleteBehavior: 'cascade'`; must not contain `many_to_many`, `deleteBehavior: 'set_null'`. - `objectstack-data/evals/objects-fields-relationships.json` case 9 (new, item 6): a multi-tenant index for an organization's open tickets. Must contain `indexes`, `organization_id`; `must_not_contain` is empty, because a careful answer warns against `tenant_id` by name, as the case's own `expected_output` does (seat review N1). - `objectstack-data/evals/hooks-security-seeds-datasources.json` case 11 (new, item 5; renumbered from 10 when #31127's case 10 landed on `main`): a read-only external `ext_supplier`. Must contain `ObjectSchema.create`, `remoteName`, `sharingModel: 'public_read'`; must not contain `columnName:`. - `objectstack-query/evals/filters-pagination-search.json` case 7 (new, item 8): tasks with no notes, where a blank string counts as none. Must contain `$empty: true`; `must_not_contain` is empty, because an answer that warns against the retired `$regex` / `$options` by name must not fail (seat review N1). - Item 7: no case changed (see § 7). No existing case encoded a corrected claim: I grepped every touched skill's `evals/` for `tenant_id`, `objectstack-ai/objectui`, `18.1.0` and the three dead anchors, with no hits. ## Other sites found (not changed: outside this card's file surface) - `content/docs/releases/v12.mdx` L204 and `v14.mdx` L298 link the archived `objectui` CHANGELOG. These are release notes, which Human Minimum item 7 says not to hand-edit. The other `objectstack-ai/objectui` hits under `content/docs/frontend/**` are historical issue links, which are correct as they are. - No other skill file holds a complete `ObjectSchema.create` example without `sharingModel` (swept: every `skills/**/*.md` with a create call). ## Verification <details><summary>Local gates: 97 derived, 97 exit 0, 0 NOT MEASURED</summary> - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 97 commands for the 20 changed paths. Each one ran under `timeout 900` with `NODE_OPTIONS=--max-old-space-size=8192`, and its exit code was captured before any pipe. The full list with exit codes is in the project folder, `card-31114/gates-e6dcc651.txt`. - A first pass on the pre-amend commit had 5 reds: 4 from the environment, 1 real. `check-plugin-teardown-shape --self-test` exited 3 because this shallow clone lacks its pinned fixture commit; fixed with `git fetch origin 621a487…`. `check:skill-examples` (exit 3), `check:dts-closure` (exit 1) and `check:dual-build-cjs-loads` (exit 3) failed with no `dist/`, before `pnpm build` (exit 0) had finished. `pnpm --dir ui exec vitest run scripts/` (exit 1) caught a real defect: `check-doc-links` refused the relative `../README.md` in `skills/objectui/README.md`, which now uses an absolute URL (see § 1). The rest of that file's reds were build-dependent. - After the fix and the build, all 97 were rerun on the amended commit (`e6dcc651`): **97 exit 0**. That includes `check:skill-identifier-liveness`, `check:skill-top-level-keys`, `check:skill-compatibility`, `check:skill-frame-sync`, `check:skill-docs`, `check:skill-refs`, `check:skill-examples` (283 blocks), `check:doc-authoring`, `check:corpus-claim-drift`, the ui `scripts/` suite (4996 tests) and the token ratchet. - The branch was then rebased onto `origin/main` `9f61e06e` before the first push. Those 5 new `main` commits touch no file in this diff. The token ratchet, `check:skill-docs --check`, the commit-trailer check and the link walk were re-run on the rebased head, all exit 0. </details> - Follow-up commit `bc879f05` (seat review S1, S2, N1; 4 skill files plus the changeset bullet): `node scripts/check-skills-token-ratchet.mjs`, `check:skill-docs --check`, `check:skill-refs`, `check:skill-compatibility`, `check:skill-identifier-liveness`, `check:corpus-claim-drift`, `check:doc-authoring` and `check-empty-changeset --base origin/main` all exit 0, and all three touched eval files parse as JSON. - Merge commit `356cc7ae` (`origin/main` into the branch, no rebase): the one conflict was `hooks-security-seeds-datasources.json`, where #31127 also appended a case 10. Both cases are kept, and this PR's case is renumbered to 11, so the ids run 1-11. Nothing else in this PR's diff against `main` changed. The token ratchet, `check:skill-docs --check`, the JSON parse of all three eval files and the link walk (249 links, 0 broken) all exit 0. - `node scripts/check-commit-card-trailers.mjs --range origin/main..HEAD`: exit 0 before each push. ## Human Minimum - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. (No schema touched; only a build script under `packages/spec/scripts`.) - [x] 2. Every name I added uses the casing for its kind. - [x] 3. No formatter ran. - [x] 4. One issue, one branch off an up-to-date `main`, one pull request. - [x] 5. A change that publishes carries a changeset (`'@objectstack/skills': patch`), and both declaration lines are filled in here and in the changeset. - [x] 6. `packages/spec` touched (`scripts/build-skill-docs.ts`): its generated artifacts (`skills/README.md` index, `content/docs/ai/skills-reference.mdx`) are regenerated and committed; `check:skill-docs` is green. - [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. (The 维护者速读 section is Chinese, per the lane template.) - [x] The *Before Submitting* list in CONTRIBUTING.md is done. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01J8VVAjQXxnSqEopTLRDyHp <!-- signed: domain:skills#3 · huangyiirene · session_01J8VVAj7CvjkGXvgXzd6RZ7 --> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent a5cb4f1 commit 620b6e6

20 files changed

Lines changed: 171 additions & 46 deletions

File tree

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@objectstack/skills': patch
3+
---
4+
5+
docs(skills): drop the "separate objectui repo" teaching, list objectui in the catalog index, repair three dead anchors, and author `sharingModel` in the object examples
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
- `objectui`: the README installs the skill from `@objectstack/skills` with the pinned `experimental_sync` command the catalog README teaches, instead of `npx skills add objectstack-ai/objectui`, and its source link and `page-builder.md`'s `plugin-gantt` link point at `ui/` in this repository. The catalog README's "Related repositories" entry points there too.
11+
- The generated skill index in `skills/README.md` and the docs Skills reference now list `objectui` (eleven skills). The generator admits every skill directory with a `SKILL.md` that is not `metadata.internal`, instead of only `objectstack-*` names.
12+
- Dead links fixed: `data-hooks.md`'s reference to a hooks section the data `SKILL.md` no longer has is removed, and `plugin-lifecycle.md` / `service-registry.md` point at `references/plugin-development.md`, where the Complete Plugin Example and the Well-Known Plugin Names table live.
13+
- `bootstrap.md`'s starting `package.json` pins `18.3.0`, the version every skill now carries.
14+
- Every complete `ObjectSchema.create` example in `relationships.md`, `lifecycle.md` and `datasources.md` authors `sharingModel`, because a publish without one is refused with `security-owd-unset`. Master-detail children use `controlled_by_parent`, and the read-only external object uses `public_read`.
15+
- `indexing.md`: the tenant column is `organization_id`, injected by `systemFields.tenant`, not an authored `tenant_id`.
16+
- `objectstack-query`: teaches `$empty`, the is-empty-by-declared-type operator that the view filters `is_empty` / `is_not_empty` lower to.
17+
- `objectstack-upgrade`: the `skills remove` roster also removes a 17.x `objectui` install (sourced from `objectstack-ai/objectui`), and says `objectstack-pm-dispatch` is not in the 18 catalog.
18+
- New eval cases cover the authored `sharingModel`, the `organization_id` tenant index and `$empty`. The touched files stay inside their token ceilings by removing restatements in the same files. No schema, key or behaviour changes.

‎content/docs/ai/skills-reference.mdx‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ npm create objectstack@latest my-app
4545

4646
{/* BEGIN GENERATED: skills (packages/spec/scripts/build-skill-docs.ts) — DO NOT EDIT */}
4747

48-
ObjectStack ships **10 skills** — one per authoring domain, plus process skills for how a project is delivered. Each is self-contained: an AI assistant loads only the ones a task needs.
48+
ObjectStack ships **11 skills** — one per authoring domain, plus process skills for how a project is delivered. Each is self-contained: an AI assistant loads only the ones a task needs.
4949

5050
## Quick Reference
5151

@@ -61,6 +61,7 @@ ObjectStack ships **10 skills** — one per authoring domain, plus process skill
6161
| 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 source layout. |
6262
| 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. This is the companion skill that objectstack-data, -ui, -api and -automation each tell you to load alongside them. |
6363
| 10 | [Upgrade](#upgrade) | `process` | `skills/objectstack-upgrade/` | Upgrade an ObjectStack metadata project across a protocol major — run the deterministic conversion chain, then work the semantic residue the chain cannot express (intent choices, custom code on retired APIs, stale prose) to a decision with the project's owner, and finish with a green `validate` plus a human-readable upgrade report. |
64+
| 11 | [ObjectUI](#objectui) | `ui` | `skills/objectui/` | Universal Server-Driven UI (SDUI) engine for building JSON-driven React interfaces with Shadcn design quality. |
6465

6566
---
6667

@@ -204,6 +205,20 @@ Do not use to author new metadata (the domain skills cover that), or to reconcil
204205

205206
---
206207

208+
### ObjectUI
209+
210+
**Domain** `ui` · **Path** `skills/objectui/`
211+
212+
Universal Server-Driven UI (SDUI) engine for building JSON-driven React interfaces with Shadcn design quality.
213+
214+
Use for schema-driven page building, plugin development, expression bindings, data integration, testing, auth/permissions, i18n, mobile responsiveness and project setup with the `@object-ui/*` packages. Triggers on ObjectUI, SchemaRenderer, JSON UI schemas, SDUI, metadata-driven UIs, `@object-ui/*`.
215+
216+
Do NOT use for server-side ObjectStack concerns (data modelling, API endpoints, automation, formulas, agents) — those belong to the `objectstack-*` skills.
217+
218+
**Tags:** `sdui`, `schema-renderer`, `json-ui`, `react`, `shadcn`, `tailwind`, `plugin`, `expression`
219+
220+
---
221+
207222
{/* END GENERATED: skills */}
208223

209224
## Skill anatomy

‎packages/spec/scripts/build-skill-docs.ts‎

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ const DISPLAY: Array<{ name: string; label: string }> = [
119119
{ name: 'objectstack-i18n', label: 'i18n' },
120120
{ name: 'objectstack-formula', label: 'Formula' },
121121
{ name: 'objectstack-upgrade', label: 'Upgrade' },
122+
{ name: 'objectui', label: 'ObjectUI' },
122123
];
123124

124125
// ── Frontmatter parser ───────────────────────────────────────────────────────
@@ -152,7 +153,12 @@ function parseFrontmatter(name: string, label: string): Skill {
152153
let tags: string[] = [];
153154

154155
for (let i = 0; i < lines.length; i++) {
155-
if (/^description:\s*>/.test(lines[i])) {
156+
// A plain one-line scalar (`description: text…`) is the other shape a
157+
// catalog entry uses (objectui's); the folded `>` block is read below.
158+
const plain = lines[i].match(/^description:\s*([^>\s].*)$/);
159+
if (plain) {
160+
description = plain[1].replace(/\s+/g, ' ').trim();
161+
} else if (/^description:\s*>/.test(lines[i])) {
156162
const buf: string[] = [];
157163
i++;
158164
while (i < lines.length && /^\s+\S/.test(lines[i])) {
@@ -178,8 +184,9 @@ function parseFrontmatter(name: string, label: string): Skill {
178184

179185
// Split the description into summary / use-when / do-not-use, keeping each
180186
// clause verbatim (no paraphrasing — the frontmatter is the source).
181-
const useIdx = description.search(/\bUse when\b/);
182-
const notIdx = description.search(/\bDo not use\b/);
187+
// objectui spells the clauses "Use for" / "Do NOT use"; the first match wins.
188+
const useIdx = description.search(/\bUse (?:when|for)\b/);
189+
const notIdx = description.search(/\bDo not use\b/i);
183190
const summary = (useIdx >= 0 ? description.slice(0, useIdx) : description).trim();
184191
const useWhen =
185192
useIdx >= 0 ? description.slice(useIdx, notIdx >= 0 ? notIdx : undefined).trim() : '';
@@ -274,17 +281,32 @@ function spliceBlock(file: string, block: string, style: CommentStyle): string {
274281
return content.slice(0, b) + block + content.slice(e + end.length);
275282
}
276283

284+
/**
285+
* The `metadata.internal` predicate `template-consistency.test.ts` applies to a
286+
* SKILL.md frontmatter, spelled the same way so the two cannot disagree.
287+
*/
288+
function isMarkedInternal(raw: string): boolean {
289+
const fm = /^---\n([\s\S]*?)\n---/.exec(raw)?.[1] ?? '';
290+
return /^metadata:\s*$/m.test(fm) && /^ +internal:\s*true\s*(#.*)?$/m.test(fm);
291+
}
292+
277293
// ── Main ─────────────────────────────────────────────────────────────────────
278294

279295
function main() {
280296
const check = process.argv.includes('--check');
281297

282298
assertWatchHintsDeclareTheReadSurface();
283299

284-
// Catalog ⇄ DISPLAY must be in lockstep.
300+
// Catalog ⇄ DISPLAY must be in lockstep. A catalog entry is every child that
301+
// carries a SKILL.md and is not `metadata.internal` — the boundary
302+
// `template-consistency.test.ts` (create-objectstack) holds the published
303+
// tree to — not a name prefix, which left `objectui` out of the index.
285304
const onDisk = fs
286305
.readdirSync(SKILLS_DIR)
287-
.filter((d) => d.startsWith('objectstack-') && fs.existsSync(path.resolve(SKILLS_DIR, d, ENTRYPOINT_FILE)));
306+
.filter((d) => {
307+
const entry = path.resolve(SKILLS_DIR, d, ENTRYPOINT_FILE);
308+
return fs.existsSync(entry) && !isMarkedInternal(fs.readFileSync(entry, 'utf-8'));
309+
});
288310
const configured = new Set(DISPLAY.map((d) => d.name));
289311
const missing = onDisk.filter((d) => !configured.has(d));
290312
const extra = DISPLAY.filter((d) => !onDisk.includes(d.name)).map((d) => d.name);

‎skills/README.md‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ apps too).
5454
| [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 source layout. |
5555
| [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. This is the companion skill that objectstack-data, -ui, -api and -automation each tell you to load alongside them. |
5656
| [Upgrade](./objectstack-upgrade/SKILL.md) | `process` | Upgrade an ObjectStack metadata project across a protocol major — run the deterministic conversion chain, then work the semantic residue the chain cannot express (intent choices, custom code on retired APIs, stale prose) to a decision with the project's owner, and finish with a green `validate` plus a human-readable upgrade report. |
57+
| [ObjectUI](./objectui/SKILL.md) | `ui` | Universal Server-Driven UI (SDUI) engine for building JSON-driven React interfaces with Shadcn design quality. |
5758

5859
<!-- END GENERATED: skills -->
5960

@@ -133,6 +134,7 @@ A few common decision points where the right skill isn't obvious:
133134

134135
---
135136

136-
## Related repositories
137+
## Related code
137138

138-
- [`../objectui`](https://github.com/objectstack-ai/objectui) — Studio UI (separate repo).
139+
- [`ui/`](https://github.com/objectstack-ai/objectstack/tree/main/ui) — the ObjectUI
140+
packages (`@object-ui/*`), in this repository.

‎skills/objectstack-data/evals/hooks-security-seeds-datasources.json‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,16 @@
100100
"must_contain": ["beforeInsert", "beforeUpdate", "language: 'js'", "'api.read'", "ctx.api.object("],
101101
"must_not_contain": ["handler:", "HookContext", "'http.fetch'"]
102102
}
103+
},
104+
{
105+
"id": 11,
106+
"prompt": "Expose the remote `suppliers` table of our `warehouse` datasource as a read-only `ext_supplier` object every member can read.",
107+
"expected_output": "A complete `ObjectSchema.create` with `datasource: 'warehouse'`, `external: { remoteName: 'suppliers' }` and an authored `sharingModel: 'public_read'` — a publish with none is refused (`security-owd-unset`), and on 'private' a member reads no row of a binding whose remote rows carry no owner.",
108+
"files": [],
109+
"assertions": {
110+
"must_contain": ["ObjectSchema.create", "remoteName", "sharingModel: 'public_read'"],
111+
"must_not_contain": ["columnName:"]
112+
}
103113
}
104114
]
105115
}

‎skills/objectstack-data/evals/objects-fields-relationships.json‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,38 @@
136136
"name: 'crm_account'"
137137
]
138138
}
139+
},
140+
{
141+
"id": 8,
142+
"prompt": "Model a `project` and its `milestone` rows; a milestone belongs to exactly one project and is deleted with it. Give me both object files.",
143+
"expected_output": "Two complete `ObjectSchema.create` calls, each with an authored `sharingModel` (a publish without one is refused with `security-owd-unset`): `project` with `sharingModel: 'private'`, `milestone` with `sharingModel: 'controlled_by_parent'` and a `master_detail` field to `project` with `deleteBehavior: 'cascade'`.",
144+
"files": [],
145+
"assertions": {
146+
"must_contain": [
147+
"ObjectSchema.create",
148+
"sharingModel: 'private'",
149+
"sharingModel: 'controlled_by_parent'",
150+
"master_detail",
151+
"deleteBehavior: 'cascade'"
152+
],
153+
"must_not_contain": [
154+
"many_to_many",
155+
"deleteBehavior: 'set_null'"
156+
]
157+
}
158+
},
159+
{
160+
"id": 9,
161+
"prompt": "Our app runs multi-tenant. Add an index on `ticket` for the list every agent opens: their organization's open tickets, newest first.",
162+
"expected_output": "`indexes: [{ fields: ['organization_id', 'status', 'created_at'] }]` — the tenant column is `organization_id`, injected by `systemFields.tenant`, so it is indexed but not declared as a field; an index naming an undeclared `tenant_id` is refused. The platform already adds the single-column `organization_id` index on a multi-tenant host.",
163+
"files": [],
164+
"assertions": {
165+
"must_contain": [
166+
"indexes",
167+
"organization_id"
168+
],
169+
"must_not_contain": []
170+
}
139171
}
140172
]
141173
}

‎skills/objectstack-data/references/data-hooks.md‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -958,7 +958,6 @@ body: { language: 'js', source: `
958958
## References
959959

960960
- `node_modules/@objectstack/spec/src/data/hook.zod.ts` — Hook schema definition, HookContext interface
961-
- [Project hooks pattern](../SKILL.md#lifecycle-hooks) — Hook integration in the data skill
962961
- [objectstack-platform/references/plugin-hooks.md](../../objectstack-platform/references/plugin-hooks.md) — plugin/kernel hooks (a different extension point)
963962
- [objectstack-automation](../../objectstack-automation/SKILL.md) — Flows and Workflows
964963
- [objectstack-platform/SKILL.md](../../objectstack-platform/SKILL.md) — hook test harnesses (vitest units, `LiteKernel`)

‎skills/objectstack-data/rules/datasources.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ An object on an external datasource binds to its remote table via `external`:
2222
ObjectSchema.create({
2323
name: 'ext_customer',
2424
datasource: 'warehouse',
25+
sharingModel: 'public_read',
2526
external: {
2627
remoteName: 'customers', // remote TABLE (object name may differ)
2728
// remoteSchema: 'public', // optional schema/namespace (pg/mysql)
@@ -32,7 +33,7 @@ ObjectSchema.create({
3233
```
3334

3435
- ❌ **`field.columnName` does not exist — on ANY object.** Authoring it is a
35-
parse error everywhere, not only on a federated object.
36+
parse error everywhere.
3637
- **Walled posture (`OS_TENANCY_POSTURE` `isolated`/`group`): every external
3738
object states its organization scope** — its own `organization_id` field (or
3839
`columnMap` onto it), `tenancy: { enabled: false }` (shared), or

‎skills/objectstack-data/rules/indexing.md‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,10 +206,16 @@ indexes: [
206206

207207
### Multi-Tenant Queries
208208

209+
The tenant column is `organization_id`, injected by `systemFields.tenant` — not a
210+
field you declare (an index naming an undeclared `tenant_id` is refused). On a
211+
multi-tenant host the platform adds its single-column index. A non-unique
212+
composite may lead with it; a unique one says `unique: 'organization'` instead
213+
(above):
214+
209215
```typescript
210-
// Query: WHERE tenant_id = X AND ...
216+
// Query: WHERE organization_id = X AND ...
211217
indexes: [
212-
{ fields: ['tenant_id', 'status', 'created_at'] },
218+
{ fields: ['organization_id', 'status', 'created_at'] },
213219
]
214220
```
215221

‎skills/objectstack-data/rules/lifecycle.md‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
Guide for declaring how long an object's data lives and how its space is
44
reclaimed (ADR-0057). Distinct from
55
**lifecycle hooks** (`beforeInsert` / `afterUpdate` …), which run logic on
6-
writes: this page covers the rows' retention, rotation and archival
7-
— and, at its end, the save notice a hook hands the saver.
6+
writes: this page covers the rows' retention, rotation and archival.
87

98
## Why This Exists
109

@@ -35,6 +34,7 @@ import { ObjectSchema, Field } from '@objectstack/spec/data';
3534
// ✅ Telemetry stream: bounded by a rotation window
3635
export const ApiCallLog = ObjectSchema.create({
3736
name: 'api_call_log',
37+
sharingModel: 'private',
3838
lifecycle: {
3939
class: 'telemetry',
4040
retention: { maxAge: '14d' }, // reap past 14 days (created_at)
@@ -49,6 +49,7 @@ export const ApiCallLog = ObjectSchema.create({
4949
// ✅ Ephemeral token: TTL on its own expiry field
5050
export const ImportTicket = ObjectSchema.create({
5151
name: 'import_ticket',
52+
sharingModel: 'private',
5253
lifecycle: {
5354
class: 'transient',
5455
ttl: { field: 'expires_at', expireAfter: '1d' }, // reap 1 day after expires_at
@@ -61,10 +62,11 @@ export const ImportTicket = ObjectSchema.create({
6162
// ✅ Compliance ledger: hot window, then cold storage
6263
export const ConsentLog = ObjectSchema.create({
6364
name: 'consent_log',
65+
sharingModel: 'private',
6466
lifecycle: {
6567
class: 'audit',
6668
retention: { maxAge: '90d' },
67-
archive: { after: '90d', to: 'archive', keep: '7y' }, // must: after === maxAge
69+
archive: { after: '90d', to: 'archive', keep: '7y' },
6870
},
6971
fields: {
7072
subject: Field.text({}),
@@ -99,7 +101,7 @@ lifecycle: { class: 'telemetry' }
99101
// ❌ Policies on record-class — business truth is permanent
100102
lifecycle: { class: 'record', retention: { maxAge: '30d' } }
101103

102-
// ❌ Archive window detached from the hot window
104+
// ❌ archive.after must equal retention.maxAge
103105
lifecycle: { class: 'audit', retention: { maxAge: '90d' }, archive: { after: '30d', to: 'archive' } }
104106

105107
// ❌ Free-form durations

0 commit comments

Comments
 (0)