Skip to content

Commit d7dd47a

Browse files
docs(skills): the objectstack-platform demoStaff example teaches the manager key (#26852)
<!-- claude-projects-attribution: {"github_login":"os-steve"} --> _Requested by **Steve** · [project thread](https://claude.ai/code/project/chan_01FdL1XeUyvVNxLreh3F4Qi9?thread=cmsg_01FdL1XeUyvVNxLreh3F4Qi9BQB21tEjsnc9ZmSt8s8KfV)_ Fixes #26764 Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:`objectstack-platform` 技能的「Demo accounts」一节,示例里给演示账号写上直属上级 `manager`(填同列表另一条目的 email),正文一句话讲清它怎么解析、哪些写法在加载时被拒、以及 `os dev` 自己写 `manager_id` 并保留手工设过的上级;eval 文件加一例(第 8 例)。两行 token 棘轮按实测重钉(SKILL.md 先削三处重述再按余额 +17,eval +221),`@objectstack/skills` patch changeset。 - 为什么改:PR #26763 给 `demoStaff` 加了 `manager` 键,但技能没教;照技能写 demo 的 AI 不知道这个键,可能自己写 seed hook 去填 `manager_id`,或拿显示名当 manager,到加载时才被拒。 - 风险与回滚:只改技能文字与 eval,不动运行时;回滚即 revert 本 PR。付账删掉的三处都是同文件里的重述,规则另有归处(见 Acceptance notes)。 - 先看:① `skills/objectstack-platform/SKILL.md` § Demo accounts(示例与新句子);② `scripts/check-skills-token-ratchet.mjs` 两行 row 注释里的算术;③ `evals/config-plugins-ops.json` 第 8 例。 - 两分钟验证:`node scripts/check-skills-token-ratchet.mjs | grep objectstack-platform/` 应见 `SKILL.md is 6014 tokens (ceiling 6014; headroom 0)` 与 `config-plugins-ops.json is 1690 tokens (ceiling 1690; headroom 0)`。 - 需要你做的:批准本 PR(`skills/**` 为 Tier H,席位不 ready / 不 arm / 不入队)。 ## What changes **`skills/objectstack-platform/SKILL.md` § Demo accounts — `demoStaff` vs `devLogins`.** The example gains a second entry (`Legal Lead`) and `Legal Counsel 1` names it with `manager: 'lead@example.com'`, so the example is a stack the platform accepts (a `manager` naming no entry is refused). The prose adds one clause, read off `origin/main` `0ea90c7a`: > `manager` is another entry's `email` — the boot writes `manager_id` itself once every account exists and keeps one set by hand, so no seed hook writes it; a display name, an address no entry has, the entry itself or a loop is refused at load Each half has its source: - another entry's `email`, compared without regard to case — `packages/spec/src/system/demo-staff.zod.ts` (`manager: z.string().email(...)`, its TSDoc) and `collectDemoStaffManagerErrors` in `packages/spec/src/stack.zod.ts`; - a display name is refused at the entry (`.email('A demo account's manager is named by the email of another demoStaff entry, not by its name')`); no entry / self / loop are refused by `defineStack` with `STACK_CROSS_REFERENCE_INVALID` 422 (`stack-demo-staff.test.ts`, the `manager —` describe); - written as `manager_id` once every entry has its account, before the seed, and a manager already set is kept — `linkDemoStaffManagers` in `packages/plugins/plugin-auth/src/demo-staff.ts` (the second pass; `managersKept`). Not taught, on purpose: the alias list (`reportsTo`, `managerEmail`, `managerId`, `supervisor` → refused naming `manager`), which the schema's refusal already says, and the set-manager write's own refusals (depth, cross-organization, directory-owned), which are reported with their `code` and `reason` at boot. **`skills/objectstack-platform/evals/config-plugins-ops.json`.** Case 8: a demo account that reports to another; expected `manager: 'counsel1@example.com'` on the subordinate's `demoStaff` entry and nothing else, `os dev` writing `manager_id` itself. `must_contain`: `demoStaff`, `manager: '`, `os dev`; `must_not_contain`: `manager_id:` (a seed row or hook writing the column), `reportsTo:`. The file is hand-formatted (inline assertion arrays) and was spliced, not re-serialised; `JSON.parse` reads 8 cases. **`scripts/check-skills-token-ratchet.mjs`.** The two rows re-pinned at the measurement, each with the arithmetic and both notes in its comment (triage 6448202744, spec-seat note 6445537040, the standing ruling 「A:先削重复,余额按实测提高」): | file | before (tokens / ceiling) | after | row | |:--|:--|:--|:--| | `SKILL.md` (priced = bytes − the 20-byte stamp line) | 23980 B → 5995 / 5997 · 504 lines | 24055 B → 6014 / 6014 · 500 lines | 5997 → **6014** (+17) | | `evals/config-plugins-ops.json` | 5873 B → 1469 / 1469 · 75 lines | 6758 B → 1690 / 1690 · 85 lines | 1469 → **1690** (+221) | | whole package, the 8 authored files of `skills/objectstack-platform/**` (ratchet report, `_index.md` generator-owned and excluded) | 21 010 tokens · 2 142 lines | 21 250 tokens (+240) · 2 148 lines | — | | whole catalog, every `skills/*/SKILL.md` | 4 606 lines | 4 602 lines | — | | whole shipped bundle total (ratchet report's last line) | 213 518 | 213 758 (+240) | — | Both readings are against `origin/main` `0ea90c7a` (the merge base) and this branch's head. The SKILL.md arithmetic, in bytes, per `git diff -U0` hunk against the merge base: cuts 127 + 101 + 29 = **257**; additions 87 (example) + 245 (prose) = **332**; net +75 bytes, so the priced text is 23980 → 24055 bytes and the row moves 5997 → 6014: the 2 tokens of headroom absorbed and the measured remainder (+17) raised under the standing ruling, pinned at the measurement. The eval file's seven fixtures share no sentence (0 bytes of duplicate), so its remainder is the whole addition, +885 bytes. **`.changeset/26764-demo-staff-manager-skill.md`** — `@objectstack/skills` patch; the package's `dist` is synced from `skills/**` (`packages/skills/scripts/sync-catalog.mjs`), so the change publishes. ## Why #26764: PR #26763 (`Fixes #26616`) added the optional `manager` key to a `demoStaff` entry and recorded `Skills-docs: follow-up owed` for these two files, both at their token ceiling. An agent writing a demo stack from the skill was not told the key exists, so it could hand-roll a seed hook writing `manager_id` or name the manager by display name, and nothing catches that until load. ## Acceptance notes **The three cuts and where each rule still lives** (every one in the same file, read by the same reader): 1. The sample refusal output under the phantom-key sentence (§ Full Configuration Reference) — the "Refused" posture bullet two paragraphs down says the top level refuses an unknown key, the parse throws naming the surface and the key, and TypeScript rejects it earlier (`TS2353`). The sentence itself ("the top level refuses it and the stack fails to load") stays: `scripts/check-skill-top-level-keys.mjs` quotes it as the page's contract and its self-test pins that the page carries it (the first gate round, on `ce024605`, caught the cut; restored in `3f46225b`). −127 B. 2. The "A bad platform default is a default to fix" bullet (§ The App / Platform Boundary) — the second bullet of the same list: "A missing default, a wrong diagnostic, a shape the spec refuses — the fix is upstream. Raise it there; do not compensate for it here." −101 B. 3. The parenthetical "(memory in dev, SQL in prod)" in § Wiring a driver — step 4 of the Runtime Boot Sequence: `DriverPlugin (memory in dev, SQL in prod)`, and the drivers table. −29 B (the paragraph re-wrapped; tokens count bytes, so the re-wrap pays nothing and was not counted). A fourth cut was made and taken back on the seat's review (`02253051`): the `sys_metadata` clause of the config-generation sentence (§ Compile Artifact, "and do not add `sys_metadata` objects to an environment runtime manifest"). Its only other home is `references/plugin-development.md`, about a plugin registering those tables — a different reader from one generating `objectstack.config.ts`, who reads only SKILL.md. The +74 B is in the remainder above. No eval fixture names any cut text (`git grep` of each cut phrase over `skills/**` and `packages/spec/scripts/**`: only the SKILL.md itself). The `typescript` fence in § Demo accounts is a fragment, unmarked for `check:skill-examples` (opt-in `os:check` marker; it has none, as before). **Triage wording.** Triage 6448202744 says "⛔ It does not raise the ceiling"; the seat's order and spec-seat note 6445537040 say raise by the measured remainder under ruling A, the maintainer's standing ruling on these rows, which outranks the triage line. Read together as: room is made inside the package first, and only the measured remainder moves a row. The SKILL.md row rises by +17 after three cuts; the eval row rises by exactly its addition, with no duplicate available to cut — the same path the rows' previous re-pins took. Forced test edits: none. No test file changed. ## Tests No code changed; the suites that read these files are the gates below. `node scripts/check-skills-token-ratchet.mjs --self-test`: `79 cases pass`, exit 0. ## Verification Gate list derived with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (no paths; the change set is 4 paths vs merge base `0ea90c7a`): **64 commands**. Run on head `02253051` from the worktree after the last commit, each with stdin closed and its exit code captured before any pipe (one `COMMAND :: exit CODE` line each), then reconciled: `dispatch-gates --ran` → ``64 derived, 64 run, 0 NOT-MEASURED, 0 UNRUN`` (a DERIVED zero). **All 64 exit 0**, among them: <details><summary>the families that read the changed files</summary> - `node scripts/check-skills-token-ratchet.mjs` — `skills/objectstack-platform/SKILL.md is 6014 tokens (ceiling 6014; headroom 0)`, `evals/config-plugins-ops.json is 1690 tokens (ceiling 1690; headroom 0)`, `81 authored bundle file(s) within their ceilings`; `--self-test`: 79 cases pass. - `pnpm check:skill-top-level-keys` — self-test 14 cases pass; `SKILL.md:40 enumerates all 52 authorable top-level keys`. - `pnpm --filter @objectstack/spec run check:skill-docs` (frontmatter unchanged, in sync), `pnpm check:skill-compatibility`, `check:skill-frame-sync`, `check:skill-identifier-liveness`, `check:pm-skill-id-lint`, `check:doc-authoring`, `check:nul-bytes`, `check:role-word`. - `node scripts/check-empty-changeset.mjs --base origin/main`, `check-changeset-no-major.mjs --base origin/main`, `check-adr-0087-registration.mjs --base origin/main`, `pnpm check:pm-changeset-deadline-census`, `check:changeset-gate-self-tests`. - `pnpm check:ratchet-remedy-authority`, `check:scripts-symbol-anchors`, `check:watch-hint-literal`, `check:self-test-wired`, `check:pm-dispatch-gates` (the ratchet script's row comments). - dist-reading families after the lock-held `turbo run build --filter='./packages/**'` (75/75, VERDICT exit 0): `check:dual-build-cjs-loads`, `check:dts-closure`, `check:lean-entry-closure`, `check:published-files`, `check:sourcemap-no-sources-content`, `@objectstack/lint check:doc-formula-expressions`. </details> Round 1 (head `ce024605`) was 63 of 64 exit 0: `check:skill-top-level-keys --self-test` red on "live tree — the real page carries the anchor and the contract sentence the drift text quotes", because the first version of cut 1 removed that sentence. `3f46225b` restored it and the whole list ran green there (64/64); `02253051` (the seat's round, the `sys_metadata` clause back) re-ran the whole list again (above). `--tier` on this diff reads MANDATORY — `skills/**`, clause ① (no one-line exit); the changed-line count is well under the 3000 human-merge threshold. Not run locally, CI's: `pnpm lint` (repo-wide), the type-check lanes, Test Core / Test UI (no code or test file changed). ## Human Minimum - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. (n/a — no schema touched) - [x] 2. Every name I added uses the casing for its kind. - [x] 3. No formatter ran; `pnpm lint` passes. (repo-wide lint is CI's run; no `.ts`/`.mjs` logic changed, only comment rows and two ceilings) - [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` touched: its generated artifacts are regenerated and committed. (n/a — 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_01ADHkmTMombCWi6iByM4RdD --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 75abd91 commit d7dd47a

4 files changed

Lines changed: 75 additions & 20 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/skills': patch
3+
---
4+
5+
docs(skills): the objectstack-platform skill teaches the `demoStaff` `manager` key — another entry's email, written as `manager_id` by the development boot (#26764)
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
The Demo accounts section of `objectstack-platform/SKILL.md` now names a line manager in its `demoStaff` example and says how the key behaves: `manager` is another entry's `email`; `os dev` writes `sys_user.manager_id` itself once every demo account exists and keeps a manager someone set by hand, so no seed hook writes it; a display name, an address no entry has, the entry itself or a loop is refused at load. One eval case in `evals/config-plugins-ops.json` expects the key and forbids a seed writing `manager_id`. Three restatements elsewhere in the skill were cut against the addition, each rule keeping its other home in the file; the remainder raises the file's token ceiling by the measurement.

‎scripts/check-skills-token-ratchet.mjs‎

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -507,7 +507,32 @@ export const CEILINGS = new Map([
507507
// bytes, so the priced text is 23924 -> 23985 bytes -> 5997 tokens, ceiling
508508
// +16, pinned AT the measurement. Same ruling, same arithmetic rule as above.
509509
// ceil(($(wc -c < skills/objectstack-platform/SKILL.md) - 20) / 4) == ceil(23985 / 4)
510-
['skills/objectstack-platform/SKILL.md', 5997],
510+
// 5997 -> 6014 (card #26764, the follow-up PR #26763 owed this file): the
511+
// Demo accounts example names a line manager (`manager`, another entry's
512+
// `email`) and the prose says what the key does — the development boot
513+
// writes `manager_id` itself once every account exists and keeps one set by
514+
// hand, so no seed hook writes it; a display name, an address no entry has,
515+
// the entry itself or a loop is refused at load. Triage 6448202744 ordered
516+
// the room made inside the package; the spec seat's note 6445537040 names
517+
// the payment path, the standing ruling on this row
518+
// 「A:先削重复,余额按实测提高」. PAID DOWN FIRST — three restatements cut, each
519+
// rule keeping its other home in the file: the sample refusal output under
520+
// the phantom-key sentence (the sentence stays — `check:skill-top-level-keys`
521+
// quotes it as the page's contract — and the "Refused" posture bullet says
522+
// the top level refuses and names the key), the "bad platform default"
523+
// bullet (the "fix is upstream" bullet of the same list), and the
524+
// driver-wiring parenthetical "(memory in dev, SQL in prod)" (step 4 of
525+
// the boot sequence and the drivers table). The `sys_metadata` clause of
526+
// the config-generation sentence was cut too and then restored on the
527+
// seat's review: its only other home is references/plugin-development.md,
528+
// a different reader from one generating objectstack.config.ts. Arithmetic
529+
// in bytes: on `origin/main` the priced text was 23980 bytes (5995 tokens,
530+
// headroom 2); the cuts remove 257 and the addition is 332, so the LANDED
531+
// priced text is 24055 bytes -> 6014 tokens: the 2 tokens of headroom are
532+
// absorbed and the ceiling moves 5997 -> 6014 (+17), the measured
533+
// remainder only. Pinned AT the measurement.
534+
// ceil(($(wc -c < skills/objectstack-platform/SKILL.md) - 20) / 4) == ceil(24055 / 4)
535+
['skills/objectstack-platform/SKILL.md', 6014],
511536
// `skills/objectstack-pm-dispatch/SKILL.md` (9708) and its
512537
// `rules/dev-template.md` (1838) left this table on 2026-09-10 with the files:
513538
// the maintainer deleted the published PM skill whole (verbatim:
@@ -949,7 +974,21 @@ export const CEILINGS = new Map([
949974
// file is 5831 -> 5873 bytes -> 1469 tokens, ceiling +11, pinned AT the
950975
// measurement.
951976
// ceil($(wc -c < skills/objectstack-platform/evals/config-plugins-ops.json) / 4) == ceil(5873 / 4)
952-
['skills/objectstack-platform/evals/config-plugins-ops.json', 1469],
977+
// 1469 -> 1690 (card #26764, the eval the SKILL.md row above was paid with):
978+
// one case for a demo account that reports to another — expected
979+
// `manager: '<the other entry's email>'` on the subordinate's `demoStaff`
980+
// entry and nothing else, `os dev` writing `manager_id` itself; NO seed row
981+
// or hook writing `manager_id`, NO `reportsTo` alias. Triage 6448202744
982+
// (room inside the package first) and the spec seat's note 6445537040 (the
983+
// standing ruling, tap A verbatim: 「A:先削重复,余额按实测提高」). PAID DOWN
984+
// FIRST: the file's seven fixtures share no sentence, so there was no
985+
// duplicate to cut (0 bytes) and the remainder is the whole addition. On
986+
// `origin/main` the file was 5873 bytes (1469 tokens, headroom 0); the
987+
// fixture adds +885 bytes, so the LANDED file is 6758 bytes -> 1690 tokens,
988+
// ceiling +221, pinned AT the measurement. The raising PR's body carries
989+
// the same arithmetic.
990+
// ceil($(wc -c < skills/objectstack-platform/evals/config-plugins-ops.json) / 4) == ceil(6758 / 4)
991+
['skills/objectstack-platform/evals/config-plugins-ops.json', 1690],
953992
// 955 -> 1175: one regression case for the now-anchored comparand (the
954993
// overdue-by-grace-days prompt) — expected `{ $now: true, addDays: { $field:
955994
// 'grace_days_offset' } }` as the whole comparand of `$lt`, and NO stored

‎skills/objectstack-platform/SKILL.md‎

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,7 @@ There is deliberately **no** top-level `workflows` or `approvals` collection:
5252
an approval is a flow with Approval nodes, and record
5353
state machines are a `state_machine` validation rule on each object.
5454
A phantom key like `roles:` or `policies:` is **not** a silent
55-
no-op — the top level refuses it and the stack fails to load:
56-
57-
```
58-
defineStack validation failed (1 issue):
59-
60-
✗ (root): Unrecognized key(s) on this stack definition: `policies`. …
61-
```
55+
no-op — the top level refuses it and the stack fails to load.
6256

6357
Undeclared keys are handled per surface, and the two postures are worth
6458
keeping straight:
@@ -174,8 +168,6 @@ what the platform owns.
174168
so it is machine-visible; before resuming, confirm the version you **pin**
175169
carries the fix (merged upstream ≠ present on your pin) and re-run the
176170
defect's own reproduction.
177-
- **A bad platform default is a default to fix**, not something to work around
178-
at every call site.
179171

180172
---
181173

@@ -258,11 +250,10 @@ Drivers are the storage layer. Pick based on your environment:
258250
### Wiring a driver — you usually do not
259251

260252
Under `os dev` / `os serve` / `os start` the CLI **resolves the driver itself**
261-
from the database URL and registers `DriverPlugin` for you (memory in dev, SQL
262-
in prod). Do **not** put a driver in your config's `plugins:` array: no example
263-
app does, and the `plugins:` key is for plugins the CLI cannot infer (connector
264-
executors, your own plugins). Pick a driver by setting the DB URL, not by
265-
writing code.
253+
from the database URL and registers `DriverPlugin` for you. Do **not** put a
254+
driver in your config's `plugins:` array: no example app does, and the
255+
`plugins:` key is for plugins the CLI cannot infer (connector executors, your
256+
own plugins). Pick a driver by setting the DB URL, not by writing code.
266257

267258
Construct `DriverPlugin` yourself only when **you** own the runtime — embedding
268259
via `Runtime` / `ObjectKernel`, or a test that boots a kernel directly:
@@ -450,14 +441,19 @@ signing in with the seed password (`--admin-password`, default `admin123`) —
450441
an entry carries no `password`:
451442

452443
```typescript
453-
demoStaff: [{ name: 'Legal Counsel 1', email: 'counsel1@example.com',
454-
position: 'legal_counsel', permissionSets: ['contracts_editor'] }],
444+
demoStaff: [
445+
{ name: 'Legal Lead', email: 'lead@example.com' },
446+
{ name: 'Legal Counsel 1', email: 'counsel1@example.com', manager: 'lead@example.com',
447+
position: 'legal_counsel', permissionSets: ['contracts_editor'] }],
455448
```
456449

457450
`name` is what a seed reference resolves by; `position` / `permissionSets`
458451
name only what this stack declares (a platform set such as
459-
`admin_full_access` is refused); `os build` drops the key and a non-dev boot
460-
creates nothing. `devLogins` creates and grants nothing — it only prints an
452+
`admin_full_access` is refused); `manager` is another entry's `email` — the
453+
boot writes `manager_id` itself once every account exists and keeps one set
454+
by hand, so no seed hook writes it; a display name, an address no entry
455+
has, the entry itself or a loop is refused at load; `os build` drops the
456+
key and a non-dev boot creates nothing. `devLogins` creates and grants nothing — it only prints an
461457
existing account on the boot banner. Pick `demoStaff` whenever the demo
462458
needs an account to exist.
463459

‎skills/objectstack-platform/evals/config-plugins-ops.json‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,16 @@
7070
"must_contain": ["demoStaff", "position", "permissionSets", "os dev"],
7171
"must_not_contain": ["role:", "admin_full_access"]
7272
}
73+
},
74+
{
75+
"id": 8,
76+
"prompt": "In the contracts demo, Paralegal 1 reports to Legal Counsel 1, so the approval flow's manager step reaches the right person on a fresh `os dev`. Set that up.",
77+
"expected_output": "On Paralegal 1's `demoStaff` entry, `manager: 'counsel1@example.com'` — the `email` of Legal Counsel 1's own entry, never the display name — and nothing else: `os dev` writes `sys_user.manager_id` itself once every demo account exists, and leaves a manager an operator set by hand. No seed row or hook writing `manager_id`, no `reportsTo` / `managerEmail` alias (refused, naming `manager`); a manager naming no entry, the entry itself or a loop is refused when the stack loads.",
78+
"files": [],
79+
"assertions": {
80+
"must_contain": ["demoStaff", "manager: '", "os dev"],
81+
"must_not_contain": ["manager_id:", "reportsTo:"]
82+
}
7383
}
7484
]
7585
}

0 commit comments

Comments
 (0)