Skip to content

Commit d65cbb6

Browse files
docs(skills, spec): every published package declares one anchor-safe isDefault permission set (#25930)
<!-- claude-projects-attribution: {"github_login":"os-project-manager"} --> _Requested by **Leehom** · [project thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1XFfzvMqgJHhisBXrtmgA5T9)_ Fixes #25773 Clause-②: no Metadata-protocol: none ## Summary Consequence (ii) of maintainer ruling record 6341690661: the published skill and the marketplace reference now state that every published package declares exactly one anchor-safe `isDefault: true` permission set. Text only. No lint rule, schema key or runtime path changes, and no new gate (the ruling orders the text; a gate needs the maintainer's own word). ## What changed 1. `skills/objectstack-data/rules/security.md`, the `isDefault: true` bullet under the RBAC section. It keeps the semantics (the `everyone` baseline, ADR-0090 D5) and adds the requirement as a check an author can run: exactly one such set, anchor-safe, meaning `describeAnchorForbiddenBits(set, 'everyone')` from `@objectstack/spec/security` returns `null`. It names the two refusals that already exist (lint rule `security-anchor-high-privilege`, and the binding), says what happens with none (invited members get nothing until an admin assigns a set to each), and says that no gate counts the sets. The old "lint and boot refuse a platform capability or undeclared name" clause is folded in: it is exactly what the anchor-safe predicate refuses in `systemPermissions`, so the bullet now says the set's `systemPermissions` may name only app capabilities declared under `capabilities:`. 2. Marketplace docs. The source is the Marketplace Protocol module doc block at the top of `packages/spec/src/marketplace/marketplace.zod.ts`, which `build-docs.ts` renders as `content/docs/references/marketplace/marketplace.mdx` (AUTO-GEN, not hand-edited). That block already carries the "Developer Publishing Flow" a publisher reads, so the new section sits right after it. The exact source sentence added: "Exactly one anchor-safe default permission set (ADR-0090 D5): `isDefault: true`, and `describeAnchorForbiddenBits(set, 'everyone')` from `@objectstack/spec/security` returns `null`." The section then gives the reason and the enforcement facts. Regenerated with `pnpm --filter @objectstack/spec gen:docs`; the only regenerated diff is the new 9-line "What a published package declares" section in `marketplace.mdx` (commit f1340f3). 3. `.changeset/25773-published-package-default-permission-set.md`: `patch` for `@objectstack/skills` (ships `skills/**`) and `@objectstack/spec` (ships `src/**/*.zod.ts`, so the edited doc block is published text). ## Where the predicate and the refusals live (read at `cb0a2fcf`) - Predicate: `describeAnchorForbiddenBits` in `packages/spec/src/security/high-privilege.ts`, exported from `@objectstack/spec/security`. For `everyone` it is `describeHighPrivilegeBits` with the stack's declared capabilities as context: no platform or undeclared `systemPermissions`, no `viewAllRecords` / `modifyAllRecords`, no `allowDelete` / `allowTransfer` / `allowExport` on any object. - Author-time refusal: `packages/lint/src/validate-security-posture.ts`, rule `SECURITY_ANCHOR_HIGH_PRIVILEGE` = `security-anchor-high-privilege` (error), run on every `isDefault: true` set. - Bind-time refusal: `confirmAudienceBindingSuggestion` in `packages/plugins/plugin-security/src/suggested-audience-bindings.ts` runs the same predicate; at boot `everyoneBaseline` in `security-plugin.ts` withholds a high-privilege set from `everyone`. - Presence and count: nothing enforces them. `appDefaultPermissionSetName` (`plugin-security/src/app-default-permission-set.ts`) takes the first `isDefault` set and returns `undefined` when there is none, with no diagnostic. The text says "no gate counts them" for that reason. ## Token ratchet (`skills/**`) | Reading | Before (`cb0a2fcf`) | After (`f1340f32`) | |:--|--:|--:| | `rules/security.md` tokens (ceiling 2563, not raised) | 2563 | 2561 | | `rules/security.md` lines | 220 | 220 | | `skills/objectstack-data/**` lines | 3806 | 3806 | | all `skills/*/SKILL.md` lines (untouched) | 4383 | 4383 | | bundle total tokens (`check-skills-token-ratchet`; before = after + 2, only this file moved) | 155469 | 155467 | The new text (+66 tokens on the bullet as first drafted) is paid by deleting content in the same file, with no re-wrap counted as savings: the `stack.zod.ts` path aside on the `permissionSets:` bullet; the parenthetical after the `rls.zod.ts` source line; the parenthetical after the `field.zod.ts` source line; ADR-0105 §"Today's two postures" from the multi-tenancy reference (ADR-0093 and the tenancy-modes URL stay); "plugin-security's FieldMasker enforces it. The real channels are:" shortened to "(FieldMasker). Two more channels:"; and the capability clause folded into the anchor-safe sentence as described above. ## Gates (head `f1340f32`) Derived with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`: 113 commands. All 113 run with exit codes captured before any pipe: 113 exit 0. Six first answered exit 3 (PREREQUISITE NOT MET: `check:doc-formula-expressions`, `check:doc-security-posture`, `check:skill-examples`, `check:docs-transcript-drift`, `check:dual-build-cjs-loads`, `check:lean-entry-closure`) and answered exit 0 after a full `pnpm turbo run build` under the verify lock. `dispatch-gates --ran`: "113 derived famil(ies) accounted for — 113 run, 0 NOT-MEASURED". Also `pnpm --filter @objectstack/spec build` exit 0, `pnpm --filter @objectstack/spec typecheck` exit 0, `check:generated` exit 0 after `gen:docs`. No tests changed and no tested behaviour changed, so there is no ablation. CI families (Test Core, Build Core, Build Docs, type-check lanes) are left to this PR's check-runs. ## 维护者速读(草稿) - **改了什么**:对外发布的技能文档 `security.md` 和 marketplace 参考文档里,写明"每个发布包必须声明且只声明一个锚安全的默认权限集(`isDefault: true`)",并给出作者可自查的判据(`describeAnchorForbiddenBits(set, 'everyone')` 返回 `null`)。只改文字,不加门禁、不改代码行为。 - **为什么改**:落实裁决 6341690661 的第 (ii) 项:默认访问只通过一个机制到达组织邀请的成员;包里没有这样的权限集,新成员就什么都看不到,直到管理员逐人分配。原文只讲了含义,没讲要求。 - **风险与代价(含回滚)**:技能文件 token 不增(2563 → 2561),用删掉同文件三处源码路径旁注、一处 ADR 章节引用和一句话来抵账。回滚即还原这两个文件并重生成文档页。 - **席位意见**: - **你要做的**:这是 Tier H(`skills/**`),需要你批准后由席位落地。 ## Acceptance notes - No gate refuses a package that declares no `isDefault` set or more than one; the app-level reader `appDefaultPermissionSetName` silently takes the first. Whether a gate should enforce this at publish time is left for the maintainer's word, per the card's boundary. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_014LdiGPrydHj5PHTzYQUvoW --- _Generated by [Claude Code](https://claude.ai/code/session_014LdiGPrydHj5PHTzYQUvoW)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 493a6c4 commit d65cbb6

4 files changed

Lines changed: 42 additions & 13 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@objectstack/skills': patch
3+
'@objectstack/spec': patch
4+
---
5+
6+
docs(skills, spec): state that every published package declares exactly one anchor-safe `isDefault: true` permission set
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
`objectstack-data/rules/security.md` said what `isDefault: true` means (the `everyone` baseline, ADR-0090 D5) but not that a published package must declare one. Its `isDefault` bullet now states the requirement as a check an author can run: exactly one such set, anchor-safe, meaning `describeAnchorForbiddenBits(set, 'everyone')` from `@objectstack/spec/security` returns `null`; otherwise lint rule `security-anchor-high-privilege` and the binding refuse it. It also says what happens without one (invited members get nothing until an admin assigns a set to each) and that no gate counts the sets. The Marketplace Protocol header in `marketplace.zod.ts`, which the generated marketplace reference page renders, gains a matching "What a published package declares" section. No lint rule or schema changes. To stay within the skill file's token ceiling, three source-path asides, a section reference and one sentence are trimmed in the same file.

‎content/docs/references/marketplace/marketplace.mdx‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,15 @@ platform, and customers who install plugins.
3131
6. Publish → Approved listing goes live on marketplace
3232
```
3333

34+
### What a published package declares
35+
Exactly one anchor-safe default permission set (ADR-0090 D5): `isDefault: true`,
36+
and `describeAnchorForbiddenBits(set, 'everyone')` from `@objectstack/spec/security`
37+
returns `null`. That set is the one mechanism by which default access reaches the
38+
members an organization invites; a package that declares none reaches no invited
39+
member until an admin assigns a set to each person. Lint rule
40+
`security-anchor-high-privilege` refuses an `isDefault` set that is not anchor-safe;
41+
no gate refuses a package that declares none, or more than one.
42+
3443
### Platform Management Flow
3544
```
3645
1. Receive → Accept submissions from verified publishers

‎packages/spec/src/marketplace/marketplace.zod.ts‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,16 @@ import { CONSUMER_INSTALLABLE_TYPES } from '../kernel/plugin.zod';
2828
* 5. Review → Platform conducts automated + manual review
2929
* 6. Publish → Approved listing goes live on marketplace
3030
* ```
31-
*
31+
*
32+
* ## What a published package declares
33+
* Exactly one anchor-safe default permission set (ADR-0090 D5): `isDefault: true`,
34+
* and `describeAnchorForbiddenBits(set, 'everyone')` from `@objectstack/spec/security`
35+
* returns `null`. That set is the one mechanism by which default access reaches the
36+
* members an organization invites; a package that declares none reaches no invited
37+
* member until an admin assigns a set to each person. Lint rule
38+
* `security-anchor-high-privilege` refuses an `isDefault` set that is not anchor-safe;
39+
* no gate refuses a package that declares none, or more than one.
40+
*
3241
* ## Platform Management Flow
3342
* ```
3443
* 1. Receive → Accept submissions from verified publishers

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

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,18 @@ export const salesUser = definePermissionSet({
2121
```
2222

2323
- `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.
24+
stack definition` error naming the key; `ObjectStackDefinitionSchema` enumerates
25+
the keys.
2626
- Bits: `allowCreate` / `allowRead` / `allowEdit` / `allowDelete`, plus
2727
`allowTransfer` (ownership change), `viewAllRecords` / `modifyAllRecords`
2828
(super-user, bypass sharing).
2929
- Source: `node_modules/@objectstack/spec/src/security/permission.zod.ts`
30-
- **`isDefault: true` = the `everyone` baseline (ADR-0090 D5).** It may carry app
31-
capabilities declared under `capabilities:` (`defineCapability`) and granted via
32-
`systemPermissions`; lint and boot refuse a platform capability or undeclared name there.
30+
- **`isDefault: true` = the `everyone` baseline (ADR-0090 D5).** A published package
31+
declares **exactly one**, anchor-safe: `describeAnchorForbiddenBits(set, 'everyone')`
32+
(`@objectstack/spec/security`) returns `null`, else lint
33+
(`security-anchor-high-privilege`) and the binding refuse it. With none, invited
34+
members get nothing until an admin assigns each; no gate counts them. Its
35+
`systemPermissions` may name only app capabilities declared under `capabilities:`.
3336
- `enable.apiMethods` also restricts the HTTP surface.
3437

3538
## Assigning a permission set to a user
@@ -108,16 +111,14 @@ bridge (warns). The compiler resolves these
108111
| `current_user.positions` | the caller's positions (for `IN`; ADR-0090 D3) |
109112
| `current_user.KEY` | the set an object's `membershipKeys.KEY` declares (for `IN`) |
110113

111-
- Source: `node_modules/@objectstack/spec/src/security/rls.zod.ts` (policy
112-
shape, predicate grammar, `check` composition).
114+
- Source: `node_modules/@objectstack/spec/src/security/rls.zod.ts`.
113115
- Prefer the built-in `member_default` owner scoping (`owner_only_writes` /
114116
`owner_only_deletes`) and the object's `sharingModel` (ADR-0056 D1) over
115117
hand-written policies for the common cases.
116118

117119
## Sensitive fields — `secret` type + `requiredPermissions`
118120

119-
`maskingRule` is **live** (plugin-security's FieldMasker enforces it). The real
120-
channels are:
121+
`maskingRule` is **live** (FieldMasker). Two more channels:
121122

122123
**Encrypted-at-rest values — `type: 'secret'` (ADR-0100).** For reversible
123124
machine credentials (DB passwords, API keys, tokens): the engine encrypts the
@@ -147,16 +148,15 @@ fields: {
147148
```
148149

149150
- Source: `node_modules/@objectstack/spec/src/data/field.zod.ts`
150-
(`secret` field type, `requiredPermissions`)
151151

152152
## Multi-tenancy
153153

154154
Organization count is a **deployment posture** fact, never object metadata.
155155
`single` (default) = one logical tenant: the bootstrapped Default Organization
156156
only, a second refused (`403`); sub-units are business units. Tenants as
157157
`sys_organization` rows need a walled posture (`group`/`isolated`; open core,
158-
ADR-0132) — ⛔ never `single` + your own RLS. See ADR-0093, ADR-0105 §"Today's
159-
two postures", https://objectstack.ai/docs/deployment/tenancy-modes.
158+
ADR-0132) — ⛔ never `single` + your own RLS. See ADR-0093,
159+
https://objectstack.ai/docs/deployment/tenancy-modes.
160160

161161
Within a posture, `tenancy` on the object is the row-level knob (stamped on
162162
write, enforced on read); **strict**, two keys:

0 commit comments

Comments
 (0)