Skip to content

Commit 70972ee

Browse files
committed
fix: align grantActionSchema with DB constraint and RLS policies
The Zod grant action enum ("read", "write", "delete", "admin") did not match the DB valid_actions check constraint ("read", "create", "update", "delete") or the RLS policy lookups. As a result, "me grant create <user> <path> create" (the action the policies actually check on insert) was rejected by Zod, while "write" and "admin" passed Zod but would be rejected at the DB layer. Scoped grants for creating or updating memories were effectively impossible through the public API. Align the Zod enum, CLI help text, type casts, test fixtures, and docs to the canonical RLS/DB set: read, create, update, delete. Superuser access stays on the user.superuser column; re-granting stays on --with-grant-option. No DB migration needed — the constraint was already correct. Fixes TNT-62.
1 parent 482e0c1 commit 70972ee

8 files changed

Lines changed: 30 additions & 29 deletions

File tree

docs/access-control.md

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ me role add-member engineering alice
3434
me role add-member engineering bob
3535

3636
# Grant access to the role (all members inherit it)
37-
me grant create engineering work.projects read write create
37+
me grant create engineering work.projects read create update
3838
```
3939

4040
Roles are implemented as users with `canLogin: false`. This means grants work the same way for users and roles.
@@ -45,14 +45,14 @@ Grants control what actions a user (or role) can perform on a tree path. A grant
4545

4646
- **user** -- who receives the access
4747
- **path** -- which tree path (and all descendants)
48-
- **actions** -- what they can do: `read`, `write`, `create`, `delete`, `admin`
48+
- **actions** -- what they can do: `read`, `create`, `update`, `delete`
4949

5050
```bash
51-
# Grant read/write access to a tree branch
52-
me grant create alice work.projects read write
51+
# Grant read and create access to a tree branch
52+
me grant create alice work.projects read create
5353

5454
# Grant full access
55-
me grant create bob work read write create delete admin
55+
me grant create bob work read create update delete
5656

5757
# Check access
5858
me grant check alice work.projects.api read
@@ -65,20 +65,21 @@ Grants are hierarchical -- a grant on `work` covers `work.projects`, `work.proje
6565
| Action | Description |
6666
|--------|-------------|
6767
| `read` | Search and retrieve memories |
68-
| `write` | Update existing memories |
6968
| `create` | Create new memories |
69+
| `update` | Update existing memories |
7070
| `delete` | Delete memories |
71-
| `admin` | Manage grants and ownership |
71+
72+
Grant management and ownership are controlled separately: grants with `--with-grant-option` let a grantee re-grant their access, and ownership (`me owner set`) gives a user full admin access to a tree path. Superuser bootstrap is handled via the `superuser` flag on the user row, not via a grant action.
7273

7374
### Grant option
7475

7576
When creating a grant with `--with-grant-option`, the grantee can re-grant that same access to others:
7677

7778
```bash
78-
me grant create alice work.projects read write --with-grant-option
79+
me grant create alice work.projects read create --with-grant-option
7980
```
8081

81-
Alice can now grant `read` and `write` on `work.projects` to other users.
82+
Alice can now grant `read` and `create` on `work.projects` to other users.
8283

8384
## Ownership
8485

@@ -134,7 +135,7 @@ me role add-member team carol
134135
me grant create team "" read
135136

136137
# Grant write access to specific branches
137-
me grant create alice work.frontend read write create
138-
me grant create bob work.backend read write create
139-
me grant create carol work.infra read write create delete admin
138+
me grant create alice work.frontend read create update
139+
me grant create bob work.backend read create update
140+
me grant create carol work.infra read create update delete
140141
```

docs/cli/me-grant.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Manage tree grants.
44

5-
Grants control access to memories by tree path. A grant gives a user specific actions (read, write, create, delete, admin) on a tree path and all its descendants.
5+
Grants control access to memories by tree path. A grant gives a user specific actions (read, create, update, delete) on a tree path and all its descendants.
66

77
## Commands
88

@@ -25,7 +25,7 @@ me grant create <user> <path> <actions...> [options]
2525
|----------|----------|-------------|
2626
| `user` | yes | User name or ID. |
2727
| `path` | yes | Tree path to grant access to. |
28-
| `actions...` | yes | One or more actions: `read`, `write`, `create`, `delete`, `admin`. |
28+
| `actions...` | yes | One or more actions: `read`, `create`, `update`, `delete`. |
2929

3030
| Option | Description |
3131
|--------|-------------|
@@ -34,7 +34,7 @@ me grant create <user> <path> <actions...> [options]
3434
### Example
3535

3636
```bash
37-
me grant create alice work.projects read write create
37+
me grant create alice work.projects read create update
3838
```
3939

4040
---
@@ -82,6 +82,6 @@ me grant check <user> <path> <action>
8282
|----------|----------|-------------|
8383
| `user` | yes | User name or ID. |
8484
| `path` | yes | Tree path. |
85-
| `action` | yes | Action to check: `read`, `write`, `create`, `delete`, `admin`. |
85+
| `action` | yes | Action to check: `read`, `create`, `update`, `delete`. |
8686

8787
Reports whether access is allowed or denied.

docs/typescript-client.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,14 +219,14 @@ await me.user.delete({ id: "019..." });
219219
await me.grant.create({
220220
userId: "019...",
221221
treePath: "team.shared",
222-
actions: ["read", "write"],
222+
actions: ["read", "create"],
223223
withGrantOption: false,
224224
});
225225
const { grants } = await me.grant.list({ userId: "019..." });
226226
const { allowed } = await me.grant.check({
227227
userId: "019...",
228228
treePath: "team.shared",
229-
action: "write",
229+
action: "create",
230230
});
231231
await me.grant.revoke({ userId: "019...", treePath: "team.shared" });
232232
```

packages/cli/commands/grant.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ function createGrantCreateCommand(): Command {
2323
.description("grant tree access to a user")
2424
.argument("<user>", "user name or ID")
2525
.argument("<path>", "tree path")
26-
.argument("<actions...>", "actions: read, write, create, delete, admin")
26+
.argument("<actions...>", "actions: read, create, update, delete")
2727
.option("--with-grant-option", "allow grantee to re-grant")
2828
.action(
2929
async (user: string, path: string, actions: string[], opts, cmd) => {
@@ -43,7 +43,7 @@ function createGrantCreateCommand(): Command {
4343
const result = await engine.grant.create({
4444
userId,
4545
treePath: path,
46-
actions: actions as ("read" | "write" | "delete" | "admin")[],
46+
actions: actions as ("read" | "create" | "update" | "delete")[],
4747
withGrantOption: opts.withGrantOption ?? false,
4848
});
4949

@@ -139,7 +139,7 @@ function createGrantCheckCommand(): Command {
139139
.description("check if a user has access to a tree path")
140140
.argument("<user>", "user name or ID")
141141
.argument("<path>", "tree path")
142-
.argument("<action>", "action: read, write, create, delete, admin")
142+
.argument("<action>", "action: read, create, update, delete")
143143
.action(async (user: string, path: string, action: string, _opts, cmd) => {
144144
const globalOpts = cmd.optsWithGlobals();
145145
const creds = resolveCredentials(globalOpts.server);
@@ -157,7 +157,7 @@ function createGrantCheckCommand(): Command {
157157
const result = await engine.grant.check({
158158
userId,
159159
treePath: path,
160-
action: action as "read" | "write" | "delete" | "admin",
160+
action: action as "read" | "create" | "update" | "delete",
161161
});
162162

163163
output(result, fmt, () => {

packages/protocol/engine/grant.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ describe("grantResponse", () => {
1313
userId: "019d694f-79f6-7595-8faf-b70b01c11f99",
1414
userName: "alice",
1515
treePath: "work.projects",
16-
actions: ["read", "write"],
16+
actions: ["read", "create"],
1717
grantedBy: null,
1818
withGrantOption: false,
1919
createdAt: "2026-01-15T00:00:00.000Z",

packages/protocol/fields.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ export const searchWeightsSchema = z.object({
8989
/**
9090
* Valid actions for tree grants.
9191
*/
92-
export const grantActionSchema = z.enum(["read", "write", "delete", "admin"]);
92+
export const grantActionSchema = z.enum(["read", "create", "update", "delete"]);
9393

9494
// =============================================================================
9595
// Accounts Fields — Org, Identity, Engine

packages/server/rpc/engine/grant.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ describe("grant.create", () => {
5252
{
5353
userId: TEST_UUID,
5454
treePath: "work.projects",
55-
actions: ["read", "write"],
55+
actions: ["read", "create"],
5656
withGrantOption: false,
5757
},
5858
context,
@@ -145,7 +145,7 @@ describe("grant.get", () => {
145145
userId: TEST_UUID,
146146
userName: "alice",
147147
treePath: "work",
148-
actions: ["read", "write"],
148+
actions: ["read", "create"],
149149
grantedBy: null,
150150
withGrantOption: true,
151151
createdAt: now,
@@ -245,7 +245,7 @@ describe("grant.check", () => {
245245
});
246246

247247
const result = await handler(
248-
{ userId: TEST_UUID, treePath: "work", action: "admin" },
248+
{ userId: TEST_UUID, treePath: "work", action: "update" },
249249
context,
250250
);
251251
expect(result).toEqual({ allowed: false });

packages/server/rpc/engine/schemas.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,7 @@ describe("grantCreateSchema", () => {
505505
const result = grantCreateSchema.safeParse({
506506
userId: "019d694f-79f6-7595-8faf-b70b01c11f98",
507507
treePath: "work.projects",
508-
actions: ["read", "write"],
508+
actions: ["read", "create"],
509509
});
510510
expect(result.success).toBe(true);
511511
});
@@ -514,7 +514,7 @@ describe("grantCreateSchema", () => {
514514
const result = grantCreateSchema.safeParse({
515515
userId: "019d694f-79f6-7595-8faf-b70b01c11f98",
516516
treePath: "work",
517-
actions: ["admin"],
517+
actions: ["update"],
518518
withGrantOption: true,
519519
});
520520
expect(result.success).toBe(true);

0 commit comments

Comments
 (0)