@everystack/mcp 0.4.6 → 0.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/core.md CHANGED
@@ -104,7 +104,7 @@ const handler = createHandler(db, schema, {
104
104
  protectedFields: { profiles: ['role'] }, // Strip from writes
105
105
  rowOwnership: { posts: { column: 'authorId', userField: 'sub' } },
106
106
  hooks: { posts: { beforeCreate: async (body, user) => ({ ...body, authorId: user?.sub }) } },
107
- softDelete: { column: 'deletedAt', tables: ['posts'] },
107
+ softDelete: { column: 'deletedAt', tables: ['posts'] }, // Models: derived, don't hand-write
108
108
  maxEmbedDepth: 3,
109
109
  maxLimit: 1000,
110
110
  });
@@ -100,6 +100,7 @@ maxLimit: 1000, // Max ?limit= value (default: 10000)
100
100
  ### Other
101
101
  ```typescript
102
102
  softDelete: { column: 'deletedAt', tables: ['posts'] }, // DELETE -> UPDATE SET deletedAt
103
+ // ^ With Models this is DERIVED from each model's `softDelete: true` — do not hand-write it.
103
104
  naming: 'snake_case', // Response key format ('camelCase' default)
104
105
  ```
105
106
 
package/dist/index.cjs CHANGED
@@ -22411,9 +22411,12 @@ function registerDesignSchemaPrompt(server) {
22411
22411
  "Declare each table with `defineModel`, following these conventions:",
22412
22412
  "- UUID primary keys: `id: field.uuid().primaryKey().defaultRandom()`",
22413
22413
  "- Timestamps: `createdAt: field.timestamptz().defaultNow().notNull()`",
22414
- "- Soft delete: a `deletedAt: field.timestamptz()` field PLUS `softDelete: true` \u2014 the flag is what",
22415
- " excludes soft-deleted rows from public reads and the data API. The field alone does nothing:",
22416
- " visibility is declared, never inferred from a column name",
22414
+ "- Soft delete: a `deletedAt: field.timestamptz()` field PLUS `softDelete: true` \u2014 the field alone",
22415
+ " does nothing. The flag decides DURABILITY (true makes DELETE mark deleted_at instead of",
22416
+ " destroying the row, and hides marked rows from the data API) and, where there is a public",
22417
+ " read, VISIBILITY (deleted_at IS NULL in the anon policy). defineModel REFUSES to guess on any",
22418
+ " model with deletedAt that has a public read or can be deleted from, so it throws at import if",
22419
+ " you omit it. Never hand-write the handler softDelete config \u2014 it is derived from this flag",
22417
22420
  "- Foreign keys via relations: `field.uuid().references(() => Author)` / `belongsTo`/`hasMany`",
22418
22421
  "- Sensitive columns: `.private()` (hidden from the API); write-guarded: `.readonly()`",
22419
22422
  "- Named exports (PascalCase model var, e.g. `export const Post = defineModel('posts', \u2026)`)",
@@ -23761,7 +23764,7 @@ async function runGovernanceCli(argv) {
23761
23764
  }
23762
23765
 
23763
23766
  // src/index.ts
23764
- var version2 = (true ? "0.4.6" : null) ?? "0.3.0-dev";
23767
+ var version2 = (true ? "0.4.7" : null) ?? "0.3.0-dev";
23765
23768
  var INSTRUCTIONS = [
23766
23769
  "You govern how any agent builds everystack \u2014 a self-hosted application stack for Expo apps on AWS.",
23767
23770
  "Your job is not only to advise but to keep the build on-script: the architecture the maintainer",
@@ -34,6 +34,9 @@ export const Post = defineModel('posts', {
34
34
  updatedAt: field.timestamptz().notNull().defaultNow(),
35
35
  deletedAt: field.timestamptz(),
36
36
  },
37
+ // Required here, not optional: this model has a `deletedAt` field AND grants DELETE, so
38
+ // `defineModel` refuses to guess. See Soft Delete below for what each value means.
39
+ softDelete: true,
37
40
  abilities: [can('read'), can('create'), can('update'), can('delete')],
38
41
  });
39
42
  ```
@@ -59,11 +62,34 @@ updatedAt: field.timestamptz().notNull().defaultNow(),
59
62
  ```
60
63
 
61
64
  ### Soft Delete
62
- Add `deletedAt` for soft-deletable tables:
65
+ The field alone does nothing. Declare BOTH — the column and what it means:
63
66
  ```typescript
64
67
  deletedAt: field.timestamptz(),
68
+ softDelete: true, // on the model, beside `fields` — NOT in the handler
65
69
  ```
66
- Configure in handler: `softDelete: { column: 'deletedAt', tables: ['posts'] }`.
70
+
71
+ **`defineModel` REFUSES to guess** when a model has `deletedAt` and either a public read or a
72
+ way to DELETE (`can('delete')`, `can('manage')` — which is "all actions" — or a `privileges`
73
+ DELETE grant). Omit the line on such a model and it throws at import.
74
+
75
+ It is declared because it changes two different things, and neither default is safe:
76
+
77
+ - **Durability, on every model** — `true` makes a `DELETE` mark `deleted_at` instead of
78
+ removing the row, and then hides marked rows from the data API's reads and updates. `false`
79
+ makes `DELETE` permanent.
80
+ - **Visibility, only where there is a public read** — `true` AND-s `deleted_at IS NULL` into
81
+ the anon policy. On an owner-scoped or admin-only model the compiled SQL is identical either
82
+ way, so the durability half is the one that matters there.
83
+
84
+ Pick `true` if the row is meant to survive its own deletion — anything with a cleanup or purge
85
+ job, an audit trail, or rows other tables reference. Pick `false` if `deleted_at` here is
86
+ audit-only and a `DELETE` really should remove the row. Getting this wrong is not cosmetic: a
87
+ table whose S3 cleanup job finds work by `deleted_at IS NOT NULL` orphans every file it should
88
+ have purged if the row is hard-deleted instead.
89
+
90
+ **Do not hand-write the handler's `softDelete: { column, tables }`.** With Models, the handler
91
+ config is DERIVED from this flag (`deriveHandlerConfig`), so writing both is how the two drift
92
+ apart — and a hand-written list silently losing a table is exactly how this bug shipped.
67
93
 
68
94
  ### Foreign Keys
69
95
  ```typescript
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@everystack/mcp",
3
- "version": "0.4.6",
3
+ "version": "0.4.7",
4
4
  "description": "Governance layer that governs how any agent builds everystack — grounding, cheat gates, and Model-aware tooling over MCP",
5
5
  "license": "AGPL-3.0-only",
6
6
  "author": "Scalable Technology, Inc. <licensing@scalable.technology>",
@@ -40,9 +40,9 @@
40
40
  "tsx": "4.21.0",
41
41
  "typescript": "5.9.3",
42
42
  "zod": "3.25.67",
43
- "@everystack/cli": "0.4.49",
44
- "@everystack/model": "0.4.13",
45
- "@everystack/server": "0.4.19"
43
+ "@everystack/cli": "0.4.57",
44
+ "@everystack/model": "0.4.16",
45
+ "@everystack/server": "0.4.20"
46
46
  },
47
47
  "scripts": {
48
48
  "test": "jest",
@@ -41,9 +41,12 @@ export function registerDesignSchemaPrompt(server: McpServer): void {
41
41
  'Declare each table with `defineModel`, following these conventions:',
42
42
  '- UUID primary keys: `id: field.uuid().primaryKey().defaultRandom()`',
43
43
  '- Timestamps: `createdAt: field.timestamptz().defaultNow().notNull()`',
44
- '- Soft delete: a `deletedAt: field.timestamptz()` field PLUS `softDelete: true` — the flag is what',
45
- ' excludes soft-deleted rows from public reads and the data API. The field alone does nothing:',
46
- ' visibility is declared, never inferred from a column name',
44
+ '- Soft delete: a `deletedAt: field.timestamptz()` field PLUS `softDelete: true` — the field alone',
45
+ ' does nothing. The flag decides DURABILITY (true makes DELETE mark deleted_at instead of',
46
+ ' destroying the row, and hides marked rows from the data API) and, where there is a public',
47
+ ' read, VISIBILITY (deleted_at IS NULL in the anon policy). defineModel REFUSES to guess on any',
48
+ ' model with deletedAt that has a public read or can be deleted from, so it throws at import if',
49
+ ' you omit it. Never hand-write the handler softDelete config — it is derived from this flag',
47
50
  '- Foreign keys via relations: `field.uuid().references(() => Author)` / `belongsTo`/`hasMany`',
48
51
  '- Sensitive columns: `.private()` (hidden from the API); write-guarded: `.readonly()`',
49
52
  '- Named exports (PascalCase model var, e.g. `export const Post = defineModel(\'posts\', …)`)',
@@ -104,7 +104,7 @@ const handler = createHandler(db, schema, {
104
104
  protectedFields: { profiles: ['role'] }, // Strip from writes
105
105
  rowOwnership: { posts: { column: 'authorId', userField: 'sub' } },
106
106
  hooks: { posts: { beforeCreate: async (body, user) => ({ ...body, authorId: user?.sub }) } },
107
- softDelete: { column: 'deletedAt', tables: ['posts'] },
107
+ softDelete: { column: 'deletedAt', tables: ['posts'] }, // Models: derived, don't hand-write
108
108
  maxEmbedDepth: 3,
109
109
  maxLimit: 1000,
110
110
  });
@@ -100,6 +100,7 @@ maxLimit: 1000, // Max ?limit= value (default: 10000)
100
100
  ### Other
101
101
  ```typescript
102
102
  softDelete: { column: 'deletedAt', tables: ['posts'] }, // DELETE -> UPDATE SET deletedAt
103
+ // ^ With Models this is DERIVED from each model's `softDelete: true` — do not hand-write it.
103
104
  naming: 'snake_case', // Response key format ('camelCase' default)
104
105
  ```
105
106
 
@@ -34,6 +34,9 @@ export const Post = defineModel('posts', {
34
34
  updatedAt: field.timestamptz().notNull().defaultNow(),
35
35
  deletedAt: field.timestamptz(),
36
36
  },
37
+ // Required here, not optional: this model has a `deletedAt` field AND grants DELETE, so
38
+ // `defineModel` refuses to guess. See Soft Delete below for what each value means.
39
+ softDelete: true,
37
40
  abilities: [can('read'), can('create'), can('update'), can('delete')],
38
41
  });
39
42
  ```
@@ -59,11 +62,34 @@ updatedAt: field.timestamptz().notNull().defaultNow(),
59
62
  ```
60
63
 
61
64
  ### Soft Delete
62
- Add `deletedAt` for soft-deletable tables:
65
+ The field alone does nothing. Declare BOTH — the column and what it means:
63
66
  ```typescript
64
67
  deletedAt: field.timestamptz(),
68
+ softDelete: true, // on the model, beside `fields` — NOT in the handler
65
69
  ```
66
- Configure in handler: `softDelete: { column: 'deletedAt', tables: ['posts'] }`.
70
+
71
+ **`defineModel` REFUSES to guess** when a model has `deletedAt` and either a public read or a
72
+ way to DELETE (`can('delete')`, `can('manage')` — which is "all actions" — or a `privileges`
73
+ DELETE grant). Omit the line on such a model and it throws at import.
74
+
75
+ It is declared because it changes two different things, and neither default is safe:
76
+
77
+ - **Durability, on every model** — `true` makes a `DELETE` mark `deleted_at` instead of
78
+ removing the row, and then hides marked rows from the data API's reads and updates. `false`
79
+ makes `DELETE` permanent.
80
+ - **Visibility, only where there is a public read** — `true` AND-s `deleted_at IS NULL` into
81
+ the anon policy. On an owner-scoped or admin-only model the compiled SQL is identical either
82
+ way, so the durability half is the one that matters there.
83
+
84
+ Pick `true` if the row is meant to survive its own deletion — anything with a cleanup or purge
85
+ job, an audit trail, or rows other tables reference. Pick `false` if `deleted_at` here is
86
+ audit-only and a `DELETE` really should remove the row. Getting this wrong is not cosmetic: a
87
+ table whose S3 cleanup job finds work by `deleted_at IS NOT NULL` orphans every file it should
88
+ have purged if the row is hard-deleted instead.
89
+
90
+ **Do not hand-write the handler's `softDelete: { column, tables }`.** With Models, the handler
91
+ config is DERIVED from this flag (`deriveHandlerConfig`), so writing both is how the two drift
92
+ apart — and a hand-written list silently losing a table is exactly how this bug shipped.
67
93
 
68
94
  ### Foreign Keys
69
95
  ```typescript