Skip to content

docs(skills): the security rule states a grant's uniqueness by the declared unique: 'organization' index, in place of the retired permission_set_id column (#23615) - #23669

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-23615-grant-unique-index-skill
Oct 15, 2026
Merged

os-zhuang merged 1 commit into
mainfrom
claude/issue-23615-grant-unique-index-skill

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #23615
Clause-②: no

What

skills/objectstack-data/rules/security.md L49 read "permission_set_id is a legacy column: do not write it." The grant object sys_user_permission_set (packages/plugins/plugin-security/src/objects/sys-user-permission-set.object.ts) no longer declares that column — a write naming it is refused 400 INVALID_FIELD as an undeclared field (grant-permission-set-name.test.ts, the C7b NEGATIVE case) — so the sentence taught an AI author a key the declared shape does not have.

One line replaces it, in the paragraph that already teaches the by-name reference (permission_set takes the set's name) and its 400 VALIDATION_FAILED refusals:

One grant per user, set and organization: enforced by the `unique: 'organization'` index.

Each clause checked against the declared shape at origin/main 1ced0f82b: the object's indexes carry { fields: ['user_id', 'permission_set', 'organization_id'], unique: 'organization' } (L267; the module header L16–21 says the same), and the duplicate refusal — organization-scoped, platform-wide (organization NULL) and via update — is pinned in grant-permission-set-name.test.ts L282–347 (409, UNIQUE_VIOLATION on the wire). The retired column is not named: the reader is an AI author, and the refusal it would meet is already loud. unique: 'organization' and its NULL-safety are taught in this package at rules/indexing.md L53 and L71, so the sentence does not restate them.

Text only; no behaviour changes. One other file: .changeset/23615-grant-unique-index-skill.md (@objectstack/skills patch). The edited file ships: packages/skills/package.json files[] is dist, and sync-catalog copies skills/** into dist/skills/** byte for byte. Measured after pnpm --filter @objectstack/skills build (65 files copied, read back byte-identical): the new sentence is in dist/skills/objectstack-data/rules/security.md (1 hit) and permission_set_id is not (0 hits).

Readings (skills/**)

reading before (1ced0f82b) after (a3f679931)
security.md lines 213 213 (net 0)
skills/objectstack-data/**/*.md lines (the package) 3717 3717
all ten skills/*/SKILL.md lines (the catalog) 4408 4408
check-skills-token-ratchet, security.md 2494 tokens (ceiling 2543; headroom 49) 2502 tokens (ceiling 2543; headroom 41)

Verdict lines verbatim — before: ✓ check-skills-token-ratchet: skills/objectstack-data/rules/security.md is 2494 tokens (ceiling 2543; headroom 49). — after: ✓ check-skills-token-ratchet: skills/objectstack-data/rules/security.md is 2502 tokens (ceiling 2543; headroom 41). No ceiling moves. The replaced line is 89 characters, inside the file's existing range (its longest line is 93); no other line was re-wrapped.

Verification

Derived at a3f679931 by node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths; change set from the merge base 1ced0f82b: 2 files, +10/−1): 41 commands — the dispatch's 29 plus 12 the changeset adds. Each ran in the foreground with its exit code captured before any pipe, then --ran reconciled the record: 41 derived famil(ies) accounted for — 39 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3).

  • 39 run, every one exit 0 — among them check-skills-token-ratchet (and its --self-test), 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, check-adr-0087-registration --base origin/main, check-changeset-no-major --base origin/main, check-empty-changeset --base origin/main, check:published-files, check:pm-changeset-deadline-census, check:objectui-changeset, and check:doc-formula-expressions (after turbo run build --filter=@objectstack/formula --filter=@objectstack/lint under the shared verify lock, the fix its refusal named).
  • 2 NOT MEASURED locally, declared: pnpm check:dual-build-cjs-loads and pnpm check:lean-entry-closure exit 3 (PREREQUISITE NOT MET: they load every package's built dist/, which only a whole-workspace build supplies; not built here for a markdown edit). CI's Lint & Repo Gates builds and runs them; its check-run is the reading.
  • Control characters: grep -naP over both changed files finds none.
  • No tested behaviour changed, so no ablation. No code, no packages/* source, no generated artifact.

CI runs the full farm; the os-dev-report comment on #23615 cites this PR's check-runs by name and conclusion.

Acceptance notes

  • Out-of-scope findings: none filed. Nothing else under skills/ names permission_set_id (git grep at 1ced0f82b: one hit, this line).
  • Governed surface (skills/**), Tier H: this PR stays a draft until an authorized approval. No label written: the changeset is present, so skip-changeset does not apply.

维护者速读(草稿)

改了什么 — skills/objectstack-data/rules/security.md 权限集授予段落的最后一句。原句写「permission_set_id 是遗留列,不要写它」;现在改为「同一用户、同一权限集、同一组织只允许一条授予记录:由 unique: 'organization' 索引强制」。只改这一行,文件行数不变;另加一个 @objectstack/skills 的 patch changeset。

为什么改 — 这份技能包随 npx skills add 发到客户项目,读者是写元数据的 AI。permission_set_id 列在 #23503 之后已从对象声明中删除(写它会被 400 INVALID_FIELD 拒绝),原句教给 AI 一个它看不到、也不能用的键;新句子陈述的是声明形状里真实存在、并有测试钉住的规则(每用户、每权限集、每组织一条授予,重复写入返回 409)。

风险与代价(含回滚) — 纯文本改动,不改任何行为与代码。token 棘轮读数 2494 → 2502(上限 2543,余量 49 → 41),上限不动。回滚:revert 本 PR 即可,无数据或迁移影响。

席位意见 — (留空)

你要做的 — Tier H 面(skills/**):审阅上面那一行英文是否准确;同意则给 APPROVED review,由席位落地。不需要其他动作。


Generated by Claude Code

…clared `unique: 'organization'` index, in place of the retired `permission_set_id` column

`skills/objectstack-data/rules/security.md`'s grant paragraph closed with a
sentence naming `permission_set_id` as a legacy column not to write. The
grant object no longer declares that column (a write naming it is refused
`400 INVALID_FIELD`), so the sentence taught an AI author a key the declared
shape does not have. One line replaces it: one grant per user, set and
organization, enforced by the join object's `unique: 'organization'` index
on (`user_id`, `permission_set`, `organization_id`). Net 0 lines; the
token ratchet reads 2494 -> 2502 against ceiling 2543. The file ships in
`@objectstack/skills` (`dist/skills/**` via sync-catalog), so a patch
changeset accompanies it.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VqGhCg9wdsBXPz54uAEWg7
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation tooling labels Oct 15, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读

改了什么 — skills/objectstack-data/rules/security.md 的权限集授予段落只改一行。原句「permission_set_id 是遗留列,不要写它」,改为「同一用户、同一权限集、同一组织只允许一条授予记录,由 unique: 'organization' 索引强制」。另加一个 @objectstack/skills 的 patch changeset。文件行数不变(213 → 213)。

为什么改 — 这份技能包随 npx skills add 发到客户项目,读者是写元数据的 AI。permission_set_id 列已在 PR #23503 从对象声明中删除,现在写它会被 400 INVALID_FIELD 拒绝。原句等于教 AI 一个它看不见、也不能用的键。新句陈述的是声明里真实存在的规则:索引在 sys-user-permission-set.object.ts L267,重复授予返回 409,有测试钉住,组织级和平台级(organization 为空)两种都覆盖。

风险与代价(含回滚) — 纯文本,不改任何行为、代码或数据。token 棘轮 2494 → 2502(上限 2543 不动,余量 49 → 41)。回滚:revert 本 PR 即可。

席位意见 — 建议批准。席位对着 diff 逐句核过:新句的三个限定(用户、权限集、组织)与索引字段一一对应;被删的旧句在 skills/ 下没有其他出处;changeset 每一句都与 diff 相符。隔离的达档契约复核还没完成:复核所需的模型档位本会话额度已用尽(子代理在出结论前被 429 中止),额度恢复后席位补跑。复核 PASS 在案之前席位不会落地;你的批准可以先给,不影响这一步。CI 在当前 head:31 项通过、6 项按设计跳过、1 项(Test Core 1/6)仍在跑,落地前席位会再核一次全绿。

你要做的 — 对 PR #23669 给一个 APPROVED review。批准后由席位落地(ready、入队),不需要其他动作。

席位核对

Seat domain:skills#2 · session_01VqGhCg9wdsBXPz54uAEWg7 (os-elon-musk) · read 2026-10-15T12:03Z · head a3f67993182b1e70a4726db9937eef38e0842e0c · verdict: ACCEPT (Tier H — stays draft for an authorized approval)

Implemented-by: claude/issue-23615-grant-unique-index-skill
Reviewed-by: session_01VqGhCg9wdsBXPz54uAEWg7

  1. Shape. Draft PR against main; body first line Fixes #23615, then Clause-②: no at line start, copied from claim 6190893934. The only closing keyword in the body is that one; the only other card named is feat(plugin-security,cli,spec)!: retire sys_user_permission_set.permission_set_id; the grant key is unique per organization; the boot grant-name backfill is deleted (C7b S3) #23503, with no verb beside it. The file list is skills/objectstack-data/rules/security.md (+1/−1) and .changeset/23615-grant-unique-index-skill.md (new); no content/docs/releases/, nothing outside the claimed surface.
  2. Premises, measured by the seat on origin/main 1ced0f82b.
    • L49 was the only permission_set_id hit under skills/.
    • sys-user-permission-set.object.ts L267 declares { fields: ['user_id', 'permission_set', 'organization_id'], unique: 'organization' }.
    • grant-permission-set-name.test.ts pins 400 INVALID_FIELD for the retired column (L190–196) and the 409 duplicate refusal for organization-scoped grants, platform-wide grants (organization NULL), batch and raw-SQL inserts, and update (L282–347).
    • The new sentence's three qualifiers map one-to-one onto the index fields.
  3. Gates — check-runs pinned to head a3f6799318. 38 check-runs: 31 success, 6 skipped, 1 in_progress (Test Core (1/6)); every Lint lane, every Type Check lane, Governed Surface Queue Guard and Check Changeset read success. The pre-landing check re-reads all of them on the head of the day. The dev's local union at a3f679931 reconciles 41 derived families, with 39 run at exit 0. Two families are NOT MEASURED locally (check:dual-build-cjs-loads, check:lean-entry-closure, exit 3: they need a whole-workspace dist/); CI's Lint lanes own them and are green on this head.
  4. Deviations accepted.
    • PM assumption (d) was wrong in mechanism. packages/skills/package.json files[] is dist, and sync-catalog copies skills/** into dist/skills/**. The dev measured that the file ships (build, then grep in dist/), so the changeset is owed and present.
    • The derived gate list grew from 29 to 41 commands, because the changeset adds the release families; all 41 are accounted for.
    • The commit carries the model-free trailer pair per AGENTS.md.
    • The labeler set documentation, size/s and tooling on the PR; the dev did not write them.
  5. Changeset, sentence by sentence (seat-read prose face, TIER_DEFAULT).
    • Sentence 1: the column is undeclared and a write naming it is refused 400 INVALID_FIELD. Verified (test L190–196).
    • Sentence 2: the replacement states one grant per user, set and organization, enforced by unique: 'organization' on (user_id, permission_set, organization_id). Verified (object L267).
    • Sentence 3: the by-name reference and its refusals are unchanged. Verified: L46–48 are untouched in the diff.
    • Sentence 4: no behaviour changes. Verified: text only.
    • The bump is patch on @objectstack/skills, consistent with a text edit to a shipped file.
  6. Out of scope and landing.
    • Out-of-scope findings: none filed.
    • The dev's one observation is dropped — not a card, for the filing gate's protocol-observation class. .claude/skills/pm-dispatch/references/platform-readings.md L171 documents the single-issue page payload as payload.preloadedQueries[0].result.data.repository.issue with frontTimelineItems. On the live page (read by the seat 2026-10-15T11:59Z), the react-app.embeddedData block carrying bodyHTML holds payload.issueViewerRoute.data.repository.issue with timelineItems / backTimelineItems. Carrier: this seat's shift brief, platform-fact class.
    • At-tier contract review: OWED. The review face is skills/objectstack-data/rules/security.md (reviewFace hits), at CONTRACT_REVIEW_TIER. The isolated reviewer was launched on this head and stopped on that tier's usage limit (HTTP 429, reported to the seat by 2026-10-15T12:03Z) before it wrote any record. No record exists, and a review served below the face's tier cannot stand in for one. This PR carries needs:contract-review until a PASS record on its head is on file; the seat re-runs the review when the tier answers again.
    • Landing: Tier H. The PR stays draft with needs-user-decision, and review is requested from os-zhuang and hotlong. Landing needs both an authorized APPROVED review and the at-tier PASS. Once both are on record, this seat runs the pre-landing checks, then pr_ready + automerge_enable.

Generated by Claude Code

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 tooling

Projects

None yet

3 participants