Skip to content

docs(skills): objectstack-data teaches the field permission when and when to pick it over a validation rule - #26393

Merged
os-zhuang merged 2 commits into
mainfrom
claude/project-thread-5cs8nl
Oct 27, 2026
Merged

os-zhuang merged 2 commits into
mainfrom
claude/project-thread-5cs8nl

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Warren · project thread
Fixes #26179
Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:skills/objectstack-data/rules/security.md 新增一节「字段权限的行条件 when」:先写选择规则(所有人都锁 → 验证规则;按角色锁 → 该权限集字段项上的 when,二者不并用),再给合同示例,再列四条边界(只管编辑、仅同对象字段、按更新前的行判定、只锁不授),末尾指向文档与配方。SKILL.md 的 readonlyWhen 一行加一句「对所有人;按角色用 when」(在余量内,不抬上限)。eval 文件加第 5 题(合同提示,期望 when + readable: true,不得出现验证规则或 readonlyWhen)。两行 token 上限按实测抬高,裁决原文写在行旁。首轮门禁 check:role-word 拒了「按角色」的英文拼法(保留词),已改为 permission set。
  • 为什么改:客户的 AI 作者按技能建应用,遇到「财务审批后不得改合同法律条款」会写验证规则,把字段对所有人锁死;when 已随 18.2 发布但技能没教「选它」,能力就一直用不上。
  • 风险与回滚:只改发布技能包的文字与一个 eval 夹具,不改运行时;回滚即 revert 本 PR(两行上限随之回落)。
  • 先看:① skills/objectstack-data/rules/security.md 新节(选择规则两条、示例一段、边界四条);② scripts/check-skills-token-ratchet.mjs 两行上限及其注释引的裁决;③ evals/hooks-security-seeds-datasources.json 第 5 题。
  • 两分钟验证:node scripts/check-skills-token-ratchet.mjs → 末行 81 authored bundle file(s) within their ceilings,其中 rules/security.md is 2956 tokens (ceiling 2956; headroom 0)、hooks-security-seeds-datasources.json is 1095 tokens (ceiling 1095; headroom 0)。
  • 需要你做的:Tier H(skills/**),请 approve 或直接 Merge when ready。

What changes

Card #26179, items 1–4:

  1. rules/security.md — a when section (placed before "Sensitive fields"): what it is (a row condition on a field entry's edit vote, in the canonical CEL of rowLevelSecurity[].using), the choice rule stated first, the contract example from the card ('clm_contract.governing_law': { readable: true, editable: false, when: "status in ['approved', 'signing', 'active']" }), then the boundaries: edit vote only (readable: false beside when is refused), same-object fields (no relation reach; refused at os validate / publish), pre-image on update (a write cannot unlock itself by moving status back; an insert on the row as sent), locks and never grants (unmatched rows fall back to the other grants; another held set's editable: true still wins).
  2. The choice rule, as a rule: a lock that holds for everyone is a validation rule (and readonlyWhen likewise strips the write for everyone); a lock that depends on who edits is when on the field entries of each permission set that loses the edit. Never both for one need. (check:role-word, ADR-0090 D3, refuses the reserved word in prose, so the teaching says "permission set" where the card says role.)
  3. Eval 5 in evals/hooks-security-seeds-datasources.json, in the file's existing shape: the contract prompt; expected output names when on the finance set's entries with readable: true and no validation rule / readonlyWhen; must_not_contain pins validations: and readonlyWhen.
  4. Pointers: the section ends with the docs anchor field-level-security#row-conditioned-edit-locks-when and the recipe anchor access-recipes#data-lock-fields-for-a-position-once-a-record-reaches-a-stage (both exist on main). references/_index.md is generator-owned (gen:skill-refs from SKILL_MAP; permission.zod.ts is already listed), so the pointer lives in security.md, not the index.

Also: one clause on SKILL.md's readonlyWhen line — the place an author reaches for the lock-for-everyone — naming the per-set alternative. It lands inside the file's headroom (raw 6104 → 6125 tokens; the ratchet's priced count 6120, ceiling 6123), no raise.

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 on origin/main 756220c4, rules/security.md carries no repeated sentence and nothing the package states twice (its secret / requiredPermissions teaching is the only full one; SKILL.md:305 and field-types.md:45 are one-line table cells), and the eval file repeats nothing — so there was nothing to cut and no surviving-home list to give. Step two, raise by the measured remainder: rules/security.md 2563 → 2956 (+395 landed on 2561, 2 absorbed by headroom, ceiling +393); evals/hooks-security-seeds-datasources.json 894 → 1095 (+201, zero headroom). Both rows re-pinned at zero headroom; the comments beside them quote the ruling.

Two readings (lines, and tokens as the ratchet counts them, ceil(utf8 bytes / 4); the SKILL.md token reading here is the raw file, the ratchet's priced count excludes the version line):

File Lines before → after Tokens before → after
rules/security.md 220 → 249 (+29) 2561 → 2956 (+395)
SKILL.md 465 → 466 (+1) 6104 → 6125 (+21)
evals/hooks-security-seeds-datasources.json (not .md, reported for the raise) 45 → 55 (+10) 894 → 1095 (+201)
Whole package (SKILL.md + references/**/*.md + rules/**/*.md, 14 files) 3705 → 3735 (+30) 39046 → 39462 (+416)

Nothing removed: the card names no stale line in this package.

Sequencing (zero verbs beside these numbers): #26297 edits the same rules/security.md and is dispatched after this lands; #26244 adds a line to references/_index.md, which this PR does not touch; #26178 is the companion docs card, already closed.

skip-changeset: the published skills packages carry no package.json; nothing is released from any package.

Tests

  • node scripts/check-skills-token-ratchet.mjs — exit 0: 81 authored bundle file(s) within their ceilings, the two raised rows at headroom 0, SKILL.md at headroom 3.
  • 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 code block is a fragment, deliberately not marked os:check, like the file's RLS block).
  • Eval 5 is a fixture (the repo has no eval runner): its assertions were checked by hand against the expected output — every must_contain string occurs in it, neither must_not_contain string does.

Acceptance notes

  • evals/README.md (headroom 1 token) was not updated to name when in its topic list; a one-line mention would need another raise for five tokens. Not filed: observation only.
  • The dispatch order's "skills/** inside the package's ceilings" cannot hold at the measured headroom (security.md 2, eval 0); the seat widened the surface to the two ratchet rows under the ruling above.
  • The PR body's first two lines are the harness-written attribution block (the os-dev exception); Fixes #26179 follows immediately.

Verification

Gate list derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on the final tree (756220c4 base, head 5f62d15b), each run locally with the exit code captured before any pipe; --ran reconciliation below.

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 is pnpm check:dual-build-cjs-loads: its prerequisite is a whole-repo build (111 packages without dist/ on this fresh worktree); this diff touches no package source, and CI's Lint & Repo Gates runs it — a declared narrowing. Three derived gates refused on a missing build on the first pass (check:doc-formula-expressions, check:lean-entry-closure, check:skill-examples); the named closures were built under scripts/pm/os-verify-lock.sh (slot issue-26179: formula+lint 94 s, objectql 61 s, client-react 149 s) and the gates then exit 0 on the final head. check:role-word was red on the first head (the reserved word in the new prose, ADR-0090 D3) and is green on 5f62d15b. check:pm-dispatch-gates ran detached on the final head: 332.2 s fast tier, 1901 cases, exit 0.

Every derived command with its exit code on 5f62d15 (54 lines: the 52 derived, plus `check:skill-refs` and `check:skill-examples` the dispatch order asked for)
  • node scripts/check-ci-filter-parity.mjs — exit 0
  • node scripts/check-closing-keyword-parity.mjs — exit 0
  • node scripts/check-closing-keyword-parity.mjs --self-test — exit 0
  • node scripts/check-comment-mask-corpus.mjs — exit 0
  • node scripts/check-declaration-mirrors.mjs — exit 0
  • node scripts/check-declaration-mirrors.mjs --self-test — exit 0
  • node scripts/check-doc-route-spelling.mjs --advisory — exit 0
  • node scripts/check-doc-route-spelling.mjs --self-test — exit 0
  • node scripts/check-dts-emitted.mjs --self-test — exit 0
  • node scripts/check-scripts-symbol-anchors.mjs — exit 0
  • node scripts/check-scripts-symbol-anchors.mjs --self-test — exit 0
  • node scripts/check-self-test-wired.mjs — exit 0
  • node scripts/check-self-test-wired.mjs --self-test — exit 0
  • node scripts/check-self-test-workflow-commands.mjs — exit 0
  • node scripts/check-self-test-workflow-commands.mjs --self-test — exit 0
  • node scripts/check-skills-token-ratchet.mjs — exit 0
  • node scripts/check-skills-token-ratchet.mjs --self-test — exit 0
  • node scripts/check-whole-set-label-write.mjs — exit 0
  • node scripts/check-whole-set-label-write.mjs --self-test — exit 0
  • node scripts/ci/assert-turbo-remote-cache.mjs --self-test — exit 0
  • node scripts/ci/prune-turbo-cache.mjs --self-test — exit 0
  • node scripts/pm/bare-root-worklist.mjs --self-test — exit 0
  • pnpm --filter @objectstack/lint run check:doc-formula-expressions — exit 0
  • pnpm --filter @objectstack/spec run check:skill-docs — exit 0
  • pnpm check:agent-test-spelling — exit 0
  • pnpm check:bash32-floor — exit 0
  • pnpm check:cli-command-ids — exit 0
  • pnpm check:corpus-claim-drift — exit 0
  • pnpm check:cross-package-test-inputs — exit 0
  • pnpm check:doc-authoring — exit 0
  • pnpm check:driver-memory-census — exit 0
  • pnpm check:dts-closure — exit 0
  • pnpm check:dual-build-cjs-loads — NOT MEASURED (exit 3, PREREQUISITE NOT MET)
  • pnpm check:entry-guard — exit 0
  • pnpm check:gitlink-declared — exit 0
  • pnpm check:lean-entry-closure — exit 0
  • pnpm check:merge-driver — exit 0
  • pnpm check:nul-bytes — exit 0
  • pnpm check:parse-guard — exit 0
  • pnpm check:pm-governed-merges — exit 0
  • pnpm check:pm-skill-id-lint — exit 0
  • pnpm check:pnpm-filter-targets — exit 0
  • pnpm check:pnpm-flag-spellings — exit 0
  • pnpm check:ratchet-remedy-authority — exit 0
  • pnpm check:refd-timer-probe — exit 0
  • pnpm check:role-word — exit 0
  • pnpm check:skill-compatibility — exit 0
  • pnpm check:skill-frame-sync — exit 0
  • pnpm check:skill-identifier-liveness — exit 0
  • pnpm check:sourcemap-no-sources-content — exit 0
  • pnpm check:watch-hint-literal — exit 0
  • pnpm --filter @objectstack/spec run check:skill-refs — exit 0
  • pnpm --filter @objectstack/spec run check:skill-examples — exit 0
  • pnpm check:pm-dispatch-gates — exit 0
Token ratchet on 5f62d15
  • skills/objectstack-data/rules/security.md is 2956 tokens (ceiling 2956; headroom 0)
  • skills/objectstack-data/evals/hooks-security-seeds-datasources.json is 1095 tokens (ceiling 1095; headroom 0)
  • skills/objectstack-data/SKILL.md is 6120 tokens (ceiling 6123; headroom 3)
  • 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 5f62d15

264 prose examples type-check across 3 surface(s) — every marked block parsed, so tsc ran the SEMANTIC pass on all of them (the new block in security.md is a fragment and is deliberately unmarked, like the file's RLS block).


Generated by Claude Code

objectstack-fleet Bot and others added 2 commits October 27, 2026 01:25
…d when to pick it over a validation rule

`rules/security.md` gains a section on the row-conditioned edit lock: the
choice rule (a lock for everyone is a validation rule; a lock that depends
on who edits is `when` on that set's field entries, never both), the
contract example, the four boundaries (edit vote only, same object,
pre-image on update, locks and never grants) and the docs pointers.
`SKILL.md`'s `readonlyWhen` line names the per-role alternative inside the
file's headroom. One eval case pins the contract prompt. The two touched
token-ratchet rows are raised by the measured remainder under the
maintainer's ruling quoted beside them.

Claude-Session: https://claude.ai/code/session_01JQwxbakp4nDuCkULsj7Fyr
Co-authored-by: Claude <noreply@anthropic.com>
`check:role-word` (ADR-0090 D3) refused the new teaching's wording in
`rules/security.md` and `SKILL.md`; the choice rule now says "permission
set". The security.md ceiling row follows the landed count (2956).

Claude-Session: https://claude.ai/code/session_01JQwxbakp4nDuCkULsj7Fyr
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 27, 2026
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation labels Oct 27, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Reviewed at 2026-10-27T02:05Z · PR #26393 · card #26179 · independent contract-review subagent of seat domain:skills#2 (seat session session_01LqKsGyMNFwwnbF5FTAqyPn).

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 5f62d15bbce8c7b03608b13e7f8390ed9453086f
Local-runs: none
Scope: skills/objectstack-data/rules/security.md, skills/objectstack-data/SKILL.md, skills/objectstack-data/evals/hooks-security-seeds-datasources.json, scripts/check-skills-token-ratchet.mjs + packages/spec/src/security/permission.zod.ts, packages/core/src/security/effective-object-permissions.ts, packages/plugins/plugin-security/src/permission-evaluator.ts, packages/plugins/plugin-security/src/security-plugin-object-inputs.ts, packages/lint/src/validate-rls-predicate-enforceability.ts, packages/spec/src/data/field.zod.ts, packages/objectql/src/validation/rule-validator.ts, packages/formula/src/rls-predicate.ts, skills/objectstack-data/rules/validation.md, skills/objectstack-data/evals/README.md, skills/objectstack-data/references/_index.md, content/docs/permissions/field-level-security.mdx, content/docs/permissions/access-recipes.mdx, .github/workflows/lint.yml, .claude/agents/os-dev.md, eslint.config.mjs

Inputs read: card #26179 body and all five comments (triage 6419206885, dispatch order 6430830929, claim 6430921692, claim amendment 6431446401, os-dev-report 6431770879); audit #26184 comment 6425678185 § objectstack-data (row D1 names this card) and the shared Acceptance of the eight cards; the PR body, file list, diff and reviews; the head's check-runs; every tree read via git show 5f62d15b:PATH or git show origin/main:PATH (origin/main = abb90460, the PR's base at my read; the branch's merge-base is 756220c4). Nothing built, run or re-run; the token figures below are byte counts of the blobs, folded by the script's stated convention ceil(utf8 bytes / 4) (a reading, not a probe).

① Derived judgments

(a) Every new sentence is true of the spec and the implementation on origin/main. Read adversarially, clause by clause, against the schema and the evaluator (not the dev's report):

  • when is a row condition on a set's fields entry, canonical CEL like rowLevelSecurity[].using, a field of the entry's object vs a literal or current_user.* — packages/spec/src/security/permission.zod.ts:973-996 (the .describe()), right.
  • Edit vote only; readable: false beside when refused at parse time — the .refine at permission.zod.ts:1003-1004 (FIELD_PERMISSION_WHEN_ON_READABLE, lines 919-923) and fieldPermissionVote at packages/core/src/security/effective-object-permissions.ts:676-684 (read bit taken as declared under every verdict; only the edit bit is withheld), right.
  • Same object, no relation reach, refused at os validate / publish — packages/lint/src/validate-rls-predicate-enforceability.ts:1922-1946 walks permissions[].fields["OBJECT.FIELD"].when and judges it against the key's object with the RLS passes; the INVALID_FIELD hint at lines 1437-1440 says a row filter reaches only the stored columns of its own object; the rule runs under the CLI commands and the runtime publish gate (lines 185-189, 48), right.
  • Pre-image on update; the row as sent on insert — packages/plugins/plugin-security/src/security-plugin-object-inputs.ts (the write gate): the judged image is row.preImage() for a by-id update, read before the write, and each incoming row of an insert on its own (the block that begins [#25866 / ruling of record 6408558941], through the rows constant and the row.operation === 'update' branch); a row that cannot be judged leaves the entry as its lock, right. "A write cannot unlock itself by moving status back" follows: the stored row, not the merged one, is judged.
  • Locks, never grants; most-permissive merge — fieldPermissionVote returns null on unmatched (the entry is absent), and permission-evaluator.ts:480 folds the votes most-permissively, so a co-held set's editable: true still wins, right.
  • readonlyWhen strips the write for everyone (security.md) and (for everyone; per permission set → field permission when) (SKILL.md:220-222): packages/objectql/src/validation/rule-validator.ts:934-935 says isSystem is deliberately NOT an exemption for the readonlyWhen lock, so "for everyone" is exact — stronger than the readonly: true bullet beneath it, which exempts system writers; right. The [validation rule](./validation.md) link resolves (rules/validation.md is in the head tree).
  • The example when: "status in ['approved', 'signing', 'active']" is the card's own contract example verbatim; the single-quoted string literal is the form the docs use (content/docs/permissions/field-level-security.mdx:37) and the predicate rewriter treats ' as a quoted literal (packages/formula/src/rls-predicate.ts:75); the spec's own example spells the same predicate with double quotes — both parse, right.
  • The pointers resolve: content/docs/permissions/field-level-security.mdx:26 heading ## Row-conditioned edit locks (when) → slug row-conditioned-edit-locks-when; content/docs/permissions/access-recipes.mdx:44 heading ### Data: lock fields for a position once a record reaches a stage → slug data-lock-fields-for-a-position-once-a-record-reaches-a-stage; the https://objectstack.ai/docs/… host form is the one security.md:188 already uses. Both anchors exist on origin/main (the companion docs card docs(permissions): a recipe for "lock these fields for this role once the record reaches a stage" — the field permission when and its Studio column, in access-recipes and the administrator guide #26178 landed), right.
  • The choice rule (everyone → validation rule / readonlyWhen; some permission sets → when per set that loses the edit; never both) is the card's Wanted 2 stated as a rule, placed first, and is consistent with SKILL.md ~L220 (which now names the per-set alternative). check:role-word forced "permission set" where the card says role — the card's Wanted 2 itself says "a role or permission set", so no meaning is lost.

(b) Cuts. The diff removes nothing (the one −1 is the re-wrapped SKILL.md line, every clause kept). Ruling A's step one (cut duplicates first): I scanned every file of the package on the head for restatements of the new section's clauses (readonlyWhen, for everyone, row-blind, pre-image, most-permissive, editable: true|false, validation rule, row condition, field permission) — the only hits outside the new section are SKILL.md's own readonlyWhen teaching (L10, L213, L220-222, L452), evals/objects-fields-relationships.json eval 3 (readonlyWhen on a field) and data-hooks.md's hook previous pre-image, none of which restate any sentence of the new section. The dev's "no duplicate existed to cut" holds. One near-overlap, not a duplicated passage: the "Same object" bullet re-derives for when what security.md:97-100 already says of RLS predicates (no cross-object traversal, a compile error); one bullet, about a different clause, so I do not read it as a cut ruling A required.

(c) Ratchet rows pinned at the measurement; ruling quoted where the script requires. Byte counts of the head blobs, folded by ceil(utf8 bytes / 4) (scripts/check-skills-token-ratchet.mjs:44-49, 227): rules/security.md 11821 bytes → 2956 = the new row (CEILINGS line 564 on the head), headroom 0; evals/hooks-security-seeds-datasources.json 4377 bytes → 1095 = the new row (line 671), headroom 0; SKILL.md 24497 bytes minus the 20-byte version: line (the script's ONE exclusion, lines 77-113) → 6120 under the untouched 6123 row (line 404), headroom 3 — the PR body's readings are exact and the two raises are exactly the measured remainder (+393 on a landed 2561 with 2 absorbed, +201 with zero headroom; ceilings +594, tokens +596). Nothing smuggled, nothing red. The raise rule (script header lines 127-129) wants the ruling quoted in the PR body; the body quotes tap A verbatim and the row comments quote it again — both. The two-readings table: rules/security.md 220 → 249 lines, SKILL.md 465 → 466, package 3705 → 3735 lines and 39046 → 39462 tokens — I recomputed all four on 756220c4 (the branch's merge-base) and the head: exact. On today's origin/main tip the package is 3706 lines because references/_index.md gained one line in between (PR #26244, not this PR's file) — reconciled, no drift in this PR. No max-len rule in eslint.config.mjs; the long row comments are the file's existing style. The new comments sit inside the CEILINGS literal, not between its doc comment and the declaration, so check-ratchet-remedy-authority's 400-char SHRINK-ONLY window (script lines 292-300) is untouched.

(d) Eval. Eval 5 is in the file's shape (id, prompt, expected_output, files, assertions), ids 1-5 unique and sequential; the prompt is the card's contract prompt; the expected output names when on the finance set's entries with readable: true, editable: false, the legal set keeping its grant, and no validation rule / readonlyWhen — the card's Wanted 3 and the card-specific acceptance, met. Every must_contain string occurs in expected_output; must_not_contain: ["validations:", "readonlyWhen"] pins the trap. One inaccuracy in the dev's report and PR body, not in the fixture: readonlyWhen DOES occur in eval 5's expected_output (as the negative "no readonlyWhen"), so "neither must_not_contain string does" is false as written; the file's own eval 4 and the sibling file's evals 1-2 already mention their forbidden strings in prose the same way, so the convention tolerates it and the assertions still read the model's output, not the expectation. evals/README.md (12 lines, 142 tokens under a 143 ceiling) is prose: build-skill-docs.ts reads only skills/README.md and the SKILL.mds, so its topic list is not a gate input; not naming when there is an observation, not a defect.

(e) Wanted 1-4, nothing beyond. 1 the when section, in the order the dispatch order asked (rule, example, boundaries, pointers) ✓; 2 the choice rule ✓; 3 the eval ✓; 4 the index: references/_index.md is generator-owned (its first line says auto-generated; gen:skill-refs from SKILL_MAP; the ratchet lists it as generator-owned at script line 1368), so the pointer lives in security.md — a faithful reading of the card's "if the skill carries a references index" ✓. SKILL.md line 1 untouched (the hunk starts at L218) ✓. Four files, all inside the claim + amendment surface (skills/objectstack-data/** plus the two CEILINGS rows of the files this PR touches, no other row, no logic) ✓. No stale line removed — the card names none ✓.

(f) PR body. Lines 1-2 are the harness-written attribution comment and _Requested by_ line; Fixes #26179 is line 3, then Clause-②: no and Metadata-protocol: none line-initial. .claude/agents/os-dev.md:323 on origin/main carries the exact exception (harness-written attribution on the first line is left as is and reported; Fixes #N follows immediately), and the two parser-backed checks on the head (The card this PR closes must claim this branch, Part-of PR must not also close its card) are green, so the closing keyword is read. ## 维护者速读 carries the six labels in the required order; 需要你做的 names approve / Merge when ready — the one action Tier H wants; 先看 names three files with the risky hunk first; 两分钟验证 is one command and the sight it names is the script's real line shape (… is N tokens (ceiling N; headroom 0) and N authored bundle file(s) within their ceilings, script lines 896 and 984). English sections in order; log readings folded in details blocks; both readings present. Nothing names another card with a closing verb.

(g) Report and claim. os-dev-report 6431770879 carries every key of the os-dev.md JSON template plus deviations (5), line_budget and out_of_scope_findings (2) the dispatch order asked for; files_changed / rules_retired are not template keys on origin/main and are absent. Branch claude/project-thread-5cs8nl = the claim's branch; mcp_calls: 0; api_writes: 2 (push, relay pr_create) with the report comment named as the third. Commit trailers are the harness pair; no model name in the tree, the commits or the PR body (the first commit's message says "per-role" — a commit message, outside the tree check:role-word reads; the tree says "permission set").

Check-runs on 5f62d15b (read at 2026-10-27T01:59Z, not waited for): 47 of 49 complete, all success or skipped; Test Core (1/8) and Type Check · consumer gates still in progress. On origin/main's lint.yml, check-skills-token-ratchet (L5589-5590), check:skill-docs (L5326) and check:skill-refs (L5458) run in the job Type Check · source gates — green at 01:56Z; check:role-word (L2326) in Lint · docs & code guards — green; check:skill-examples (L6463) in Type Check · consumer gates — in progress at my read (the dev's local run of it exit 0 after building the closure; the new fence is deliberately unmarked, like the file's RLS block). The brief's note that the ratchet runs under Lint · repo & tooling gates is stale against today's lint.yml; that lane is green too.

② Semver level

skip-changeset is right: skills/objectstack-data/ carries no package.json (its tree is SKILL.md, evals/, references/, rules/) and scripts/** publishes nothing; Check Changeset is green on the head. Clause-②: no and Metadata-protocol: none are line-initial on the PR body (lines 4-5) and on the claim. No runtime, schema or protocol key changes — the skill teaches a key the protocol already carries (permission.zod.ts:973).

③ Boundary flags

  1. Landing order. A GOVERNED_APPROVERS account approved (review 5661354750 at 2026-10-27T01:53Z, on this head) and readied / armed auto-merge before this record; at my last read the PR was open, mergeable_state: clean, auto_merge no longer set. The Tier H path (contract review → seat's ## 席位核对 → maintainer's hand) was overtaken by the maintainer's own hand; this record is still owed on the head and stands whether the PR lands before or after it. 收班简报 note, no card.
  2. Report claim vs fixture (①(d)). "Neither must_not_contain string occurs in expected_output" is false for readonlyWhen; the fixture follows the file's convention and is fine. 收班简报 note; nothing to fix in the tree.
  3. evals/README.md topic list does not name when; prose, not a gate input; adding it costs about five tokens over a 1-token headroom and a further ruling-cited raise. Optional follow-up, not a card.
  4. Dispatch-order premise "inside the package's ceilings" was unreachable (headroom 2 and 0); already ruled by tap A on the seat thread and carried by the claim amendment. Nothing to file.
  5. Base vs tip. Merge-base 756220c4; origin/main tip abb90460 differs inside the package by one generated line in references/_index.md (PR feat(spec,metadata-protocol,service-storage,runtime,cli)!: a seed record can carry a file the package ships, seedFile(path) #26244); no overlap with this PR's hunks, the merge commit the platform computed (6ddc2641) is clean.
  6. Lane mapping. The ratchet, check:skill-docs and check:skill-refs run under Type Check · source gates on today's lint.yml, not Lint · repo & tooling gates; the seat's brief template should say so next time. 收班简报 note.

Implemented-by: claude/project-thread-5cs8nl
Reviewed-by: session_01LqKsGyMNFwwnbF5FTAqyPn

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读

改了什么:skills/objectstack-data/rules/security.md 新增一节「字段权限的行条件 when」:先写选择规则(对所有人锁 → 验证规则或 readonlyWhen;按权限集锁 → 该权限集字段项上的 when,二者不并用),再给合同示例,再列四条边界(只管编辑、仅同对象字段、按更新前的行判定、只锁不授),末尾指向文档与配方;SKILL.md 的 readonlyWhen 一行加一句「对所有人;按权限集用 when」(余量内);eval 第 5 题。两行天花板按你的裁决 A 实测抬高(本包无重复可削,PR 正文已说明)。
为什么改:AI 作者遇到「财务审批后不得改合同法律条款」会写验证规则,把字段对所有人锁死;when 随 18.2 发布但技能没教「选它」。
风险与回滚:只改发布技能包文字与一个 eval 夹具,不改运行时;回滚 = revert 本 PR(两行上限随之回落)。
先看:① rules/security.md 新节(四条边界每条都对着 spec/运行时源码核过);② scripts/check-skills-token-ratchet.mjs 两行与行旁裁决;③ eval 第 5 题。
两分钟验证:node scripts/check-skills-token-ratchet.mjs 预期末行 81 authored bundle file(s) within their ceilings,其中 rules/security.md 2956/2956、hooks-security-seeds-datasources.json 1095/1095,headroom 0。
需要你做的:无——os-zhuang 已 approve + ready + 入队(01:54Z),合入即落地。
席位意见:同意落地。复核记录在 os-zhuang 动手之后才贴上(他比席位早了约 10 分钟),结论一致:四条边界属实(permission.zod.ts:1003、effective-object-permissions.ts:676、validate-rls-predicate-enforceability.ts:1922、permission-evaluator.ts:480),无遗失规则。

席位核对

ACCEPT · domain:skills#2 · session_01LqKsGyMNFwwnbF5FTAqyPn · 2026-10-27T02:09Z — seat review of record for #26179 on head 5f62d15b, against GitHub, not the report's own account. Published surface skills/** ⇒ Tier H: the record of this tier is the independent ## Contract review at comment 6431950933 (Served-tier: CONTRACT_REVIEW_TIER, head 5f62d15b, VERDICT: PASS, Local-runs: none; drafted by a reviewer other than the implementer, the card thread's dev). This seat adds the landing read. Order of events, reported: the governed approver's hand ran ahead of this record (see 6).

  1. Shape. Base main, mergeable_state: clean; body lines 1–2 the harness attribution block, line 3 Fixes #26179 (os-dev.md 323's exemption; the closing-keyword guards green), Clause-②: no, Metadata-protocol: none line-initial (kind none ⇒ no Skills-docs: line owed under the rule landed in b33dd25b); ## 维护者速读 six labels in order, then What changes / Tests / Acceptance notes / Verification with folds. Files: rules/security.md (+29), SKILL.md (+2/−1), evals/hooks-security-seeds-datasources.json (+10), scripts/check-skills-token-ratchet.mjs (+19/−2, two rows) — the claim's surface (6430921692) as amended at 2026-10-27T01:26Z (+ the package's CEILINGS rows under ruling A); line 1 of SKILL.md untouched. Labels documentation, size/s, skip-changeset — right, no package.json under skills/objectstack-data. Branch claude/project-thread-5cs8nl, named by the claim. Model: the thread switched to the ceiling tier on the maintainer's own words before its first edit (reported in the os-dev-report).
  2. Record, measured. Every taught boundary read true of the implementation on origin/main: edit-vote only (permission.zod.ts:1003–1004; fieldPermissionVote in effective-object-permissions.ts:676–684), same-object fields only (validate-rls-predicate-enforceability.ts:1922–1946), pre-image on update (security-plugin-object-inputs.ts write gate), locks never grants (permission-evaluator.ts:480); readonlyWhen 「for everyone」 exact (rule-validator.ts:934–935). Both docs anchors resolve. Ratchet rows pinned at the measurement (security.md 11821 B → 2956; eval 4377 B → 1095; SKILL.md 6120 under the untouched 6123); no duplicate of the new clauses anywhere in the 14 .md files, so 「nothing to cut」 holds; the two-readings table exact on 756220c4. Wanted 1–4 met; _index.md generator-owned; SKILL.md line 1 untouched; four files inside claim + amendment. One harmless inaccuracy in the report (eval 5's must_not_contain note). The seat re-read the diff and reads the same.
  3. Gates, pinned to head 5f62d15b. 49 check-runs on the head all success or skipped by 2026-10-27T02:05Z. Lane map read off today's lint.yml: the token ratchet, check:skill-docs and check:skill-refs run in Type Check · source gates; check:skill-examples in Type Check · consumer gates.
  4. Accepted deviations. (a) Surface widened to the CEILINGS rows — the seat's own amendment under ruling A. (b) The card's cited commits unreachable in the shallow clone; spec lines cited instead. (c) check:dual-build-cjs-loads NOT MEASURED locally (whole-tree build prerequisite), CI's run green. (d) Fixes on line 3 under the harness block.
  5. Carrier. Nothing to file. 收班简报: the governed approver readied and armed before the contract review (reported, not judged); evals/README.md does not name when (prose, not a gate input); SeedColumn.applyOn is untaught in this package (route to skills(objectstack-data): teach membership keys, ownerMayEdit, create-only seed columns, the position-to-set binding and the external role, and fix the reference: 'user' example #26297).
  6. Landing. os-zhuang approved (5661354750), readied and armed at 2026-10-27T01:54Z; the PR sits in the merge queue — ⛔ nothing for the seat to ready, arm or enqueue. On MERGED the seat closes the card with the landing record and releases skills(objectstack-data): teach membership keys, ownerMayEdit, create-only seed columns, the position-to-set binding and the external role, and fix the reference: 'user' example #26297 (same file rules/security.md, now at headroom 0: its additions need ruling-A arithmetic again).

Generated by Claude Code

Merged via the queue into main with commit 7c70f91 Oct 27, 2026
51 checks passed
@os-zhuang
os-zhuang deleted the claude/project-thread-5cs8nl branch October 27, 2026 02:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants