Repository navigation
docs(skills): objectstack-data teaches membership keys, ownerMayEdit, create-only seed columns, the position-to-set binding and the external tier - #26416
Conversation
… create-only seed columns, the position-to-set binding and the external tier
rules/security.md: a membership-keys section (declare `membershipKeys` on
the roster object; the three members; the refusals; the once-per-request
fail-closed read; the choice rule against owner scoping, a position and a
sharing rule), then the position -> `permissionSets` binding (a namesake
set is reported, never granted) and the `external` membership tier for
portal users (no baseline, explicit grants only).
rules/relationships.md: `sharing.controlledByParent.ownerMayEdit` beside
the controlled_by_parent row.
references/seeds.md: the `columns` dataset key and `applyOn: 'create'`.
references/examples-objects.md: the two `{ type: 'lookup', reference:
'user' }` fields become `type: 'user'`.
evals: one portal membership case; the seed and invoice-line cases gain
the new keys.
scripts/check-skills-token-ratchet.mjs: the six touched objectstack-data
rows follow the measurement (five raised under the maintainer's ruling,
one lowered by the net cut).
Claude-Session: https://claude.ai/code/session_01JQwxbXTWvHG6MXFD7Gywf2
Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewReviewed at 2026-10-27T03:21Z · PR #26416, head Served-tier: Landing state. A Check-runs on ① Derived judgments(a) Every new sentence against the spec on
(b) Nothing removed beyond the stale lines the card names; nothing to cut under ruling A. The diff's only deletions are the two (c) Ratchet rows pinned at the measurement; the ruling quoted where the script requires. The measure is (d) Evals. Both files parse as JSON on the head; ids 1–6 (hooks) and 1–4 (objects) unique and sequential. Over every case of both files, each (e) Wanted lines met, nothing beyond. D2 (f) PR body dogfoods the current shape. Line 1 the harness attribution HTML comment, line 2 the (g) Report shape and claim. os-dev-report 6432762671 (2026-10-27T03:08Z) carries every key of the os-dev.md JSON template on ② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
维护者速读改了什么:objectstack-data 技能包补上审计 D2–D7 六行—— 席位核对ACCEPT ·
Generated by Claude Code |
Resolves the adjacent-row conflict in scripts/check-skills-token-ratchet.mjs: main's objectstack-automation row (#26390) and this branch's objectstack-data examples-objects.md row both kept. Claude-Session: https://claude.ai/code/session_01JQwxbXTWvHG6MXFD7Gywf2 Co-authored-by: Claude <noreply@anthropic.com>
席位核对ACCEPT (addendum, pinned to the merge head) · Generated by Claude Code |
Requested by Warren · project thread
Fixes #26297
Clause-②: no
Metadata-protocol: none
维护者速读
skills/objectstack-data教五件 18.x 已发布、技能没教的事:①rules/security.md新增「成员键」一节(membershipKeys声明在名册对象上,userField/valueField/filter三个成员、拒收规则、每请求一次的系统读且失败即零行、与 owner 作用域 / 岗位 / 共享规则的选择规则)和「岗位只授它点名的集合;门户用户是external」一节;②rules/relationships.md在controlled_by_parent旁教sharing.controlledByParent.ownerMayEdit;③references/seeds.md补columns选项行和applyOn: 'create'小节;④references/examples-objects.md两处过时的{ type: 'lookup', reference: 'user' }改为type: 'user';⑤ eval 加门户成员题一道,种子题与发票行题各扩一句。六行 token 上限按实测调整,裁决原文写在行旁。reference: 'user'的例子会被原样抄进一个不存在的对象。skills/objectstack-data/rules/security.md新两节(成员键示例块与四条要点、岗位/external两条);②scripts/check-skills-token-ratchet.mjs六行上限及其注释;③evals/hooks-security-seeds-datasources.json第 6 题。node scripts/check-skills-token-ratchet.mjs→ 末行81 authored bundle file(s) within their ceilings,其中rules/security.md is 3693 tokens (ceiling 3693; headroom 0);pnpm --filter @objectstack/spec run check:skill-examples→ 新标记的名册对象块随 265 个示例通过类型检查。skills/**),请 approve 或直接 Merge when ready。What changes
Card #26297, the six Measured rows (D2–D7) and nothing wider:
rules/security.md— "Membership keys" (after the RLS section, whose placeholder table already namedcurrent_user.KEYwithout saying where it comes from): the choice statement first (rows keyed on a named record → a key on the roster object, never a stamp on every row or a policy per record), a type-checked roster example (account_memberwithmembershipKeys.portal_account_ids: { userField: 'user', valueField: 'account', filter: 'active == true' }) and the policy fragment that reads it within; then four bullets — the three members and what each may name; the refusals (one name per stack, the kernel's own names, no contribution through an object extension); the once-per-request system read (no read on the roster needed, 1,000-value cap, a larger set / failed read / non-compilingfiltermatches no rows, whoever writes roster rows grants the key); and the choice rule against owner scoping, a position'spositionsand a sharing rule.rules/security.md— "Positions grant only what they name; portal users areexternal": a position distributes only the sets itspermissionSetsnames (ADR-0131 D3/D4; a namesake set is reported, never granted; a name matching no set confers nothing), with the one-line position shape; theexternalmembership tier for portal and partner users (closed list, one tier per member, docs anchor), which theeveryonebaseline does not reach, sees no member directory, holds no business unit, and for which every org-wide default is owner-only — rows reach it only through explicit grants (a position or set whose row policies bound them, a sharing rule, a manual share).check:role-word(ADR-0090 D3) refuses the reserved word in this tree, so the prose says "membership tier" where the spec's constant file says otherwise.rules/relationships.md— "Owner edits undercontrolled_by_parent—ownerMayEdit" under the master_detail pattern, plus "owner opt-inownerMayEdit(below)" in the type table's Sharing cell (the card's row at L16): what a detail inherits, the two-line opt-in, what it does not widen (insert, delete, predicate updates, on-behalf-of writes; the detail's own row policies still AND in), refused at parse off acontrolled_by_parentdetail and one level up.references/seeds.md: acolumnsrow in theSeedfields table and a section "Columns the app owns after the first load —columns.FIELD.applyOn" after the import modes: why a replay reverts app-owned columns,applyOn: 'create'semantics per mode, the refused key, and the cross-link to the import mapping entry'sapplyOn(taught in objectstack-api; not restated).references/examples-objects.mdL42 and L88:{ type: 'lookup', reference: 'user', … }→{ type: 'user', … }(the 18.x form; auserfield's target is fixed tosys_userand a foreignreferenceis refused). Both blocks areos:check-marked and still type-check. This is the one stale text the card names, and the only removal.hooks-security-seeds-datasources.jsoneval 6 (the card's acceptance prompt: portal staff read only the orders of the accounts they belong to — expectedmembershipKeyson the roster,current_user.portal_account_idsin the policy,userField/valueField, the set bound through a position'spermissionSets, theexternaltier;must_not_containpins the object-levelpermissions:key), and eval 4 gains the job-owned column (applyOn: 'create');objects-fields-relationships.jsoneval 2 gains the owner opt-in (ownerMayEdit) and pinsadded_byastype: 'user'withmust_not_contain: "reference: 'user'".SKILL.mdis untouched: itscontrolled_by_parentrow (L76) and the security pointer line (L48) sit at 3 tokens of headroom, and the card's Wanted namesrules/relationships.mdforownerMayEdit;references/_index.mdis generator-owned and unchanged (the spec files involved are already listed).Token-ratchet rows (
scripts/check-skills-token-ratchet.mjs), per the maintainer's ruling on the seat's decision card 「skills 修复卡的教学和 eval 都撞 token 天花板,怎么付?」, tap A (2026-10-27 01:21Z), verbatim and untranslated: 「A:先削重复,余额按实测提高」. Step one, cut duplicates first: measured onorigin/mainc51aed4f, none of the four.mdfiles carries a repeated sentence (a sentence-level scan of each file: 0 repeats insecurity.md,relationships.md,seeds.md; the only repeat inexamples-objects.mdis theos:checkmarker + import line that opens each type-checked block), and neither eval file repeats a case — so there was nothing to cut and no surviving-home list to give. Step two, each touched row follows its measurement (ceil(utf8 bytes / 4)):rules/security.md2956 → 3693 (+737 at headroom 0);rules/relationships.md3676 → 3873 (+201 landed on 3672, 4 absorbed by headroom, ceiling +197);references/seeds.md1299 → 1566 (+267 at headroom 0);references/examples-objects.md1779 → 1769 (the two stale fields are a net cut of 10 tokens; the row follows the measurement down);evals/hooks-security-seeds-datasources.json1095 → 1404 (+309);evals/objects-fields-relationships.json946 → 1050 (+104). Every row re-pinned at zero headroom; each comment quotes the tap. Onlyobjectstack-datarows are touched.Two readings (lines, and tokens as the ratchet counts them):
rules/security.mdrules/relationships.mdreferences/seeds.mdreferences/examples-objects.mdevals/hooks-security-seeds-datasources.json(not .md, reported for the raise)evals/objects-fields-relationships.json(not .md, reported for the raise)SKILL.md+references/**/*.md+rules/**/*.md, 14 files, raw file bytes)Removed: the two stale
reference: 'user'spellings only.Sequencing (zero verbs beside these numbers): #26393 landed the
whensection this PR's membership text sits next to, and is not restated; #25640 owns line 1 ofSKILL.md, which this PR does not touch; #26392 taught the import door'sapplyOnin objectstack-api, whichseeds.mdcross-links.skip-changeset: the published skills packages carry nopackage.json; nothing is released from any package.Why
An AI author building a portal or a roster-scoped app from this skill had no rule for a set keyed on a named record, so it wrote per-row policies by hand or stamped an owner on every row; it expected a position-named set to grant itself; it let a seed replay revert a status an admin or a job had advanced; and it copied
reference: 'user'into a lookup to an object that does not exist. Every key here is already carried by@objectstack/spec18.x (Metadata-protocol: none); the gap was the teaching.Tests
node scripts/check-skills-token-ratchet.mjs— exit 0:81 authored bundle file(s) within their ceilings, the six touched rows at headroom 0.node scripts/check-skills-token-ratchet.mjs --self-test— exit 0,79 cases pass.pnpm --filter @objectstack/spec run check:skill-docs,check:skill-refs,check:skill-examples— see Verification; the new roster-object block insecurity.mdisos:check-marked and type-checks (the policy fragment beside it is unmarked, like the file's RLS block).must_containstring of the new and extended cases occurs in its expected output; themust_not_containstrings were checked against the expected outputs by hand.Acceptance notes
SKILL.mdL48 (the security pointer line) does not name membership keys and L76 does not point atownerMayEdit; both would need a raise of the SKILL.md row for a few tokens. Not filed: observation only.evals/README.md(headroom 1) was not updated to name the new topics. Not filed: observation only.skills/**inside the package's ceilings" cannot hold at the measured headroom (0 on five of the six files); the seat's claim widened the surface to the touched ratchet rows under the ruling above.Fixes #26297follows immediately.Verification
Gate list derived with
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackon the final tree (basec51aed4f, heade63f6ec2), each run locally with the exit code captured before any pipe;--ranreconciliation below. No commit followed the gate runs, so every reading is ofe63f6ec2.Reconciliation (
node scripts/pm/dispatch-gates.mjs --ran ran.list --repo objectstack-ai/objectstack, exit 0):52 derived, 51 run, 1 NOT-MEASURED, 0 UNRUN. The one NOT MEASURED ispnpm check:dual-build-cjs-loads(exit 3: its prerequisite is a whole-repo build; this diff touches no package source, and CI'sLint & Repo Gatesruns it — a declared narrowing). Two readings were taken twice:pnpm check:dts-closureexited 1 on a first pass that raced the still-running closure build (plugin-auth mid-emit, "1 built package missing declaration files") and exits 0 re-run after the build (38 built package(s) swept - 160/160 declared declaration file(s) present);node scripts/check-comment-mask-corpus.mjswas run alone aftercheck:skill-examples(exit 0,18297 files, 0 disagree). The closures forcheck:skill-examplesandcheck:doc-formula-expressions(@objectstack/client-react...and@objectstack/lint...) were built underscripts/pm/os-verify-lock.sh(slotissue-26297, VERDICT command-exit 0, 251 s held).check:pm-dispatch-gatesran detached on the final head: fast tier, 1901 cases pass, 299 s, exit 0.Every derived command with its exit code on e63f6ec (53 lines: the 52 derived, plus `check:skill-docs` and `check:skill-examples` the dispatch order asked for)
node scripts/check-ci-filter-parity.mjs— exit 0node scripts/check-closing-keyword-parity.mjs— exit 0node scripts/check-closing-keyword-parity.mjs --self-test— exit 0node scripts/check-declaration-mirrors.mjs— exit 0node scripts/check-declaration-mirrors.mjs --self-test— exit 0node scripts/check-doc-route-spelling.mjs --advisory— exit 0node scripts/check-doc-route-spelling.mjs --self-test— exit 0node scripts/check-dts-emitted.mjs --self-test— exit 0node scripts/check-scripts-symbol-anchors.mjs— exit 0node scripts/check-scripts-symbol-anchors.mjs --self-test— exit 0node scripts/check-self-test-wired.mjs— exit 0node scripts/check-self-test-wired.mjs --self-test— exit 0node scripts/check-self-test-workflow-commands.mjs— exit 0node scripts/check-self-test-workflow-commands.mjs --self-test— exit 0node scripts/check-skills-token-ratchet.mjs— exit 0node scripts/check-skills-token-ratchet.mjs --self-test— exit 0node scripts/check-whole-set-label-write.mjs— exit 0node scripts/check-whole-set-label-write.mjs --self-test— exit 0node scripts/ci/assert-turbo-remote-cache.mjs --self-test— exit 0node scripts/ci/plan-test-core-shards.mjs --self-test— exit 0node scripts/ci/prune-turbo-cache.mjs --self-test— exit 0node scripts/pm/bare-root-worklist.mjs --self-test— exit 0pnpm --filter @objectstack/spec run check:skill-refs— exit 0pnpm check:agent-test-spelling— exit 0pnpm check:bash32-floor— exit 0pnpm check:cli-command-ids— exit 0pnpm check:corpus-claim-drift— exit 0pnpm check:cross-package-test-inputs— exit 0pnpm check:doc-authoring— exit 0pnpm check:driver-memory-census— exit 0pnpm check:dts-closure— exit 0pnpm check:entry-guard— exit 0pnpm check:gitlink-declared— exit 0pnpm check:nul-bytes— exit 0pnpm check:parse-guard— exit 0pnpm check:pm-governed-merges— exit 0pnpm check:pm-skill-id-lint— exit 0pnpm check:pnpm-filter-targets— exit 0pnpm check:pnpm-flag-spellings— exit 0pnpm check:ratchet-remedy-authority— exit 0pnpm check:refd-timer-probe— exit 0pnpm check:role-word— exit 0pnpm check:skill-compatibility— exit 0pnpm check:skill-frame-sync— exit 0pnpm check:skill-identifier-liveness— exit 0pnpm check:sourcemap-no-sources-content— exit 0pnpm check:watch-hint-literal— exit 0pnpm --filter @objectstack/spec run check:skill-docs— exit 0pnpm --filter @objectstack/spec run check:skill-examples— exit 0pnpm --filter @objectstack/lint run check:doc-formula-expressions— exit 0pnpm check:dual-build-cjs-loads— NOT MEASURED (exit 3, PREREQUISITE NOT MET: whole-repo build)pnpm check:lean-entry-closure— exit 0node scripts/check-comment-mask-corpus.mjs— exit 0pnpm check:pm-dispatch-gates— exit 0Token ratchet on e63f6ec
skills/objectstack-data/rules/security.md is 3693 tokens (ceiling 3693; headroom 0)skills/objectstack-data/rules/relationships.md is 3873 tokens (ceiling 3873; headroom 0)skills/objectstack-data/references/seeds.md is 1566 tokens (ceiling 1566; headroom 0)skills/objectstack-data/references/examples-objects.md is 1769 tokens (ceiling 1769; headroom 0)skills/objectstack-data/evals/hooks-security-seeds-datasources.json is 1404 tokens (ceiling 1404; headroom 0)skills/objectstack-data/evals/objects-fields-relationships.json is 1050 tokens (ceiling 1050; headroom 0)81 authored bundle file(s) within their ceilings; 10 generator-owned file(s) measured, not ratcheted.--self-test:79 cases pass.check:skill-examples on e63f6ec
265 prose examples type-check across 3 surface(s) — every marked block parsed, so tsc ran the SEMANTIC pass on all of them(264 onmain; the 265th is the newos:check-marked roster object insecurity.md).Human Minimum
Each item is one rule from CONTRIBUTING.md › Human Minimum.
packages/specholds no runtime logic. (No schema change.)pnpm lintis CI's run (repo-wide scan).main, one pull request.skip-changeset); both declaration lines are filled in above.packages/specnot touched.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.Generated by Claude Code