Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions skills/objectstack-data/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -593,14 +593,53 @@ export const salesUser = definePermissionSet({
contact: { allowRead: true },
},
});

// Register it on the stack root under `permissions` — NOT `permissionSets`:
// defineStack({ permissions: [salesUser], ... })
```

- **Stack key: `permissions`.** The collection is named for the metadata kind,
not for the factory, so `definePermissionSet()` output goes into
`defineStack({ permissions: [...] })`. `permissionSets:` is **refused at
load** — the top level is strict, so the stack fails with an
`Unrecognized key(s) on this stack definition` error naming the key, never a
silent drop. `ObjectStackDefinitionSchema`
(`node_modules/@objectstack/spec/src/stack.zod.ts`) is the enumeration of
record; `objectstack-platform` lists every top-level key.
- Bits: `allowCreate` / `allowRead` / `allowEdit` / `allowDelete`, plus
`allowTransfer` (ownership change), `viewAllRecords` / `modifyAllRecords`
(super-user, bypass sharing).
- Source: `node_modules/@objectstack/spec/src/security/permission.zod.ts`
- Combine with `enable.apiMethods` to also restrict the HTTP surface.

### Assigning a permission set to a user

Declaring a set grants nobody anything — an assignment is **data**: one row in
the join object **`sys_user_permission_set`** (`@objectstack/plugin-security`),
carrying `user_id`, `permission_set_id`, and an optional `organization_id`
(`null` = every org context). Optional `valid_from` / `valid_until` bound a
half-open window checked at resolution time; `granted_by` is stamped by the
gate on insert — never author it.

⚠️ **`permission_set_id` takes the `sys_permission_set` RECORD ID, not the set's
`name`.** Grants resolve by loading `sys_permission_set` **by `id`**, so a `name`
in that field matches nothing, raises no error, and silently grants nothing.
Declared sets are upserted by `name` with a **generated** `id` on `kernel:ready`
(ADR-0086 D5) — that id differs per environment, so resolve it first.

Assignment is therefore two calls, both `POST /api/v1/data/{object}`
(`…/query` with a QueryAST body for the read): look up the set's `id` in
`sys_permission_set` by `name`, then insert
`{ user_id, permission_set_id, organization_id }` into
`sys_user_permission_set`. Only a tenant admin — or a delegated `adminScope`
carrying `manageAssignments` for that set and user (ADR-0090 D12) — may write
it; plain CRUD bits on the table are not enough.

**Grant looks inert?** Check in order: a `name` in `permission_set_id`; the set
is `active: false`; the validity window has passed; `organization_id` mismatch.
`GET /api/v1/security/explain?object=&operation=&userId=` answers from the
enforcing code path (explaining another user needs `manage_users`).

### Access depth (scope-depth) — the ERP "see my unit / my unit and below" axis

For owner-scoped (`private`) objects, a per-object grant on a permission set can
Expand Down
1 change: 1 addition & 0 deletions skills/objectstack-platform/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -1273,6 +1273,7 @@ describe('stack boot', () => {
| LiteKernel test passes, ObjectKernel boot fails | Test missed a plugin the CLI auto-registers — compare your test's `use()` list against the `os dev` boot log |
| Hot reload misses new objects | Barrel `src/objects/index.ts` not re-exporting — check the file |
| Login works but **Setup / Studio missing** | The logged-in user isn't a platform admin. Setup/Studio are gated by `setup.access` / `studio.access` on `admin_full_access`, auto-granted only to the first registered **human** (`bootstrapPlatformAdmin`). The `usr_system` seed identity is skipped, so it can't steal the grant. Either sign up first (`--seed-admin`/`--fresh` does this) or check `sys_user_permission_set` for a cross-tenant (`organization_id = NULL`) `admin_full_access` link on your user. Don't edit nav code first. |
| A permission set is declared but grants nobody anything | Declaring a set is not assigning it. Assignment is a `sys_user_permission_set` row whose `permission_set_id` is the `sys_permission_set` **record id**, never the set's `name` — see "Assigning a permission set to a user" in **objectstack-data**. |

---

Expand Down
Loading