Skip to content

Commit 0949fe5

Browse files
hotlongclaude
andauthored
docs(skills): objectstack-data's security rules name the risk of renaming or removing a shipped access item in a later version (#25429)
Clause-②: no Metadata-protocol: none ## What this does It adds a risk note to `skills/objectstack-data/rules/security.md`, the access-control rules of the published data-modelling skill: **renaming or removing a shipped access item in a later package version can quietly undo an administrator's choice on upgrade.** Administrators act on a package's permission sets, positions, sharing rules and capabilities by name. They switch items off, clone sets, and reference the package's objects and fields from their own sets. So in a later version: - a **rename** makes the old switch-off stop matching, and the renamed item arrives switched on; - a **removal** leaves a customer's clone or own set naming something that no longer exists; - an **added field deny** on a shipped set is not carried into a customer's clone. The note asks authors to keep shipped names stable and, when such a change is genuinely needed, to say in the release notes what an administrator should re-check. It is guidance only: no gate and no behaviour change. This is the maintainer's ruling of 2026-10-23: 「这种应该写到元数据应用的开发规范中」「提示风险就行」. ## Why here This came out of objectstack-ai/cloud#3173 (ADR-0126 wall 3 on cloud's install paths). The only package on the cloud marketplace is first-party (HotCRM), so the maintainer chose a development-convention note over a runtime check or a publish gate. ## The token budget The file sat at its ratchet ceiling, so the new text is paid for by deleting restatements in the same file. No rule changes meaning. Each deletion: 1. **The RBAC stack-key bullet.** The "`permissions`, not `permissionSets`" fact is the code sample's own comment just above it. 2. **"takes the set's `name`".** This repeats the sentence above it. 3. **The assignment payload.** It repeats the columns listed two sentences earlier. 4. **The `view_pii` sample's comment.** It repeats the sentence directly above it. 5. **The `viewAllRecords` corollary.** It follows from "it never crosses the Layer 0 wall" in the same bullet. 6. **The callout tail.** It restates the two Layer 0 bullets and the recipe's AND-gate comment; the ADR-0095 D1 citation stays in the first bullet. The changes were written at the tier that `dispatch-gates.mjs` mandates for `skills/**`. ## Verification Run on head `3d46b7b8`: - `check-skills-token-ratchet`: `security.md` is 2541 / 2543 tokens; `--self-test` passes. - `check:doc-authoring`, `check:agent-test-spelling`, `check:role-word`, `check:skill-compatibility`, `check:skill-frame-sync`, `check:skill-identifier-liveness`, `check:pm-skill-id-lint`, `check:corpus-claim-drift` and `check-doc-route-spelling --advisory`: each exits 0. **Noticed, not touched:** the assignment paragraph still says an `organization_id` of `null` means every organization context. Since #24583 step 7, an organization-less grant confers nothing, so that clause looks stale. Fixing it changes a rule, so it is left for its own change. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent b53057a commit 0949fe5

2 files changed

Lines changed: 38 additions & 20 deletions

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@objectstack/skills': patch
3+
---
4+
5+
docs(skills): objectstack-data's security rules name the risk of renaming or removing a shipped access item in a later package version
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
`skills/objectstack-data/rules/security.md` gains a closing section, "Renaming or removing a shipped access item". Administrators act on a package's permission sets, positions, sharing rules and capabilities by name: they switch them off, clone them, and name the package's objects, fields and capabilities in their own sets. The section says what a rename, a removal or an added field deny does to those choices on upgrade, asks authors to keep shipped names stable and, when such a change is genuinely needed, to say in the release notes what changed and what an administrator should re-check. It is guidance only: no gate and no behaviour change.
11+
12+
The file stays under its token ceiling. The new text is paid for by deleting restatements elsewhere in the same file: the stack-key bullet that repeated the code sample's comment, the assignment payload that repeated the row's columns, a code comment that repeated the sentence above it, the `viewAllRecords` corollary sentence, and the callout's repeats of the Layer 0 bullets and of a citation made two paragraphs earlier. No rule changes meaning.

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

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,9 @@ export const salesUser = definePermissionSet({
2020
// defineStack({ permissions: [salesUser], ... })
2121
```
2222

23-
- **Stack key: `permissions`** (named for the metadata kind, not the factory).
24-
`permissionSets:` is **refused at load** — the strict top level fails with an
25-
`Unrecognized key(s) on this stack definition` error naming the key, never a
26-
silent drop; `ObjectStackDefinitionSchema`
27-
(`node_modules/@objectstack/spec/src/stack.zod.ts`) is the enumeration of record.
23+
- `permissionSets:` is **refused at load** with an `Unrecognized key(s) on this
24+
stack definition` error naming the key; `ObjectStackDefinitionSchema`
25+
(`node_modules/@objectstack/spec/src/stack.zod.ts`) enumerates the keys.
2826
- Bits: `allowCreate` / `allowRead` / `allowEdit` / `allowDelete`, plus
2927
`allowTransfer` (ownership change), `viewAllRecords` / `modifyAllRecords`
3028
(super-user, bypass sharing).
@@ -43,13 +41,11 @@ optional `organization_id` (`null` = every org context). Optional `valid_from` /
4341
`valid_until` bound a half-open window; `granted_by` is stamped by the gate on
4442
insert — never author it.
4543

46-
⚠️ **`permission_set` takes the set's `name`, never a record id** — a name the
47-
security catalog does not hold is refused `400 VALIDATION_FAILED`
48-
(`reference_not_found`), and a row with no name is refused `required`.
44+
⚠️ **Never a record id** — a `permission_set` the security catalog does not hold
45+
is refused `400 VALIDATION_FAILED` (`reference_not_found`); none at all, `required`.
4946
One grant per user, set and organization: enforced by the `unique: 'organization'` index.
5047

51-
Assignment is one call: `POST /api/v1/data/sys_user_permission_set` with
52-
`{ user_id, permission_set: '<name>', organization_id }`. Only a tenant admin —
48+
Assignment is one `POST /api/v1/data/sys_user_permission_set`. Only a tenant admin —
5349
or a delegated `adminScope` carrying `manageAssignments` for that set and user
5450
(ADR-0090 D12) — may write it; plain CRUD bits on the table are not enough.
5551

@@ -144,7 +140,7 @@ over permission-set field grants.
144140
fields: {
145141
ssn: {
146142
type: 'text',
147-
requiredPermissions: ['view_pii'], // mask on read / deny on write without it
143+
requiredPermissions: ['view_pii'],
148144
},
149145
}
150146
```
@@ -188,9 +184,7 @@ none — e.g. identity tables a plugin writes via its own adapter
188184
better-auth-managed identity table). It never crosses the Layer 0 wall —
189185
crossing takes a *true platform admin* (the superuser bit **and** a
190186
platform-exclusive capability: `manage_metadata`, `manage_platform_settings`,
191-
`studio.access`, `manage_users`) on one of those same postures. So an org
192-
admin with the superuser bit stays org-scoped; on an ordinary tenant object
193-
nobody crosses (the admin sees 0 rows too).
187+
`studio.access`, `manage_users`) on one of those same postures.
194188

195189
**Recipe — env-global, admin-only object that admins can fully see:**
196190

@@ -205,9 +199,21 @@ requiredPermissions: ['manage_platform_settings'], // capability AND-gate → me
205199
> reads every row env-wide — the shipped `viewer_readonly` still carries one, as
206200
> may an app-declared default profile or a customer-authored set. (The
207201
> `member_default` baseline is **not** one of them: it is explicit-allow and
208-
> grants only the objects it names.) `requiredPermissions` *by itself* leaves the
209-
> object a tenant object, so the wall keeps denying the untagged rows and even a
210-
> platform admin sees nothing. The pair is the correct combo; `requiredPermissions`
211-
> is the half that holds however permissive the caller's grants are (an AND-gate
212-
> checked **before** the CRUD grant). Posture model: ADR-0066; tenant wall:
213-
> ADR-0095 D1.
202+
> grants only the objects it names.) `requiredPermissions` *by itself* leaves a
203+
> tenant object, whose untagged rows the wall denies to everyone (above); it is
204+
> the half that holds however permissive the caller's grants are. Posture model:
205+
> ADR-0066.
206+
207+
## Renaming or removing a shipped access item
208+
209+
Administrators act on a package's access items by **name**: they switch a
210+
permission set, position or sharing rule off, clone a set, and name its objects,
211+
fields and capabilities in their own sets. A later version can quietly undo them
212+
on upgrade:
213+
214+
- **Rename** → the old switch-off stops matching; the renamed item arrives switched **on**.
215+
- **Remove** an object, field or capability → a customer's clone or own set names something that no longer exists.
216+
- **Add a field deny** to a shipped set → a customer's clone does not carry it.
217+
218+
Keep shipped names stable. When such a change is genuinely needed, say so in the
219+
release notes: what changed, and what an administrator should re-check after upgrading.

0 commit comments

Comments
 (0)