@everystack/cli 0.4.82 → 0.4.84

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/CHANGELOG.md CHANGED
@@ -4,6 +4,50 @@ All notable changes to `@everystack/cli` are documented here.
4
4
 
5
5
  Format based on [Keep a Changelog](https://keepachangelog.com/).
6
6
 
7
+ ## [Unreleased]
8
+
9
+ ### Added
10
+
11
+ - automate the version-drift check — a manual note is not a gate
12
+
13
+ ## [0.4.84] - 2026-09-16
14
+
15
+ _Internal changes only._
16
+
17
+ ## [0.4.83] - 2026-09-16
18
+
19
+ ### Added
20
+
21
+ - db:pull --derived-out takes a directory, split by kind and schema
22
+ - a real SELECT-only role for db:query's read connection
23
+ - version-guard the Model-derived wire payloads (provision, converge-ownership, reconcile)
24
+ - two symbols and plain words — the default output for db:plan/db:apply/db:check
25
+ - db:apply does the whole job — the plan, then the functions and views
26
+
27
+ ### Fixed
28
+
29
+ - derive USAGE only on serial sequences (least privilege) (#11)
30
+ - close the closing-gate findings on --derived-out <dir>
31
+ - owner-adoption fast-follows — identity guard, self-grant revoke, ENACT transaction
32
+ - mirror the reader fields in ServerProvision, and pin that no credential prints
33
+ - an E-string prefix only counts when it stands alone
34
+ - throw a private sentinel instead of calling tx.rollback() — closes a console-sandbox swallow/neutralize hole
35
+ - the shape-pin also covers enum value domains, not just field names
36
+ - symmetric v-prefix in the mismatch message, restore db:provision's --direct detail
37
+ - ADD CONSTRAINT/PRIMARY KEY are not additive — they can fail against existing data
38
+ - the lease covers the catch-up, the receipt marks a strip, the preflight never exits mid-chain
39
+ - the catch-up's refusal mode belongs to the caller, and the failure line says why
40
+ - the catch-up failure names two subjects, quotes its cause once, and nobody inherits 'exit'
41
+ - db:apply refuses a malformed plan `statements` instead of throwing
42
+ - what a statement IS gets read once, and a comment can neither hide a revoke nor invent a drop
43
+ - the declared side rides on the plan, so a strip reads the same before and after
44
+ - one statement, one bucket — the summary's two halves read the same declared side
45
+ - the plan's optional grantee set is refused at load, not dereferenced after the write
46
+
47
+ ### Documentation
48
+
49
+ - the workflow is three commands — db:plan, db:apply, db:check
50
+
7
51
  ## [0.4.82] - 2026-09-08
8
52
 
9
53
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@everystack/cli",
3
- "version": "0.4.82",
3
+ "version": "0.4.84",
4
4
  "description": "CLI and OTA updates for Expo apps on everystack",
5
5
  "license": "AGPL-3.0-only",
6
6
  "author": "Scalable Technology, Inc. <licensing@scalable.technology>",
@@ -48,6 +48,11 @@
48
48
  "types": "./src/cli/db-source.ts",
49
49
  "default": "./src/cli/db-source.ts"
50
50
  },
51
+ "./wire": {
52
+ "source": "./src/wire.ts",
53
+ "types": "./src/wire.ts",
54
+ "default": "./src/wire.ts"
55
+ },
51
56
  "./reconcile": {
52
57
  "source": "./src/reconcile.ts",
53
58
  "types": "./src/reconcile.ts",
@@ -120,15 +125,17 @@
120
125
  "@aws-sdk/client-rds": "3.1053.0",
121
126
  "@aws-sdk/client-s3": "3.1053.0",
122
127
  "@aws-sdk/client-ssm": "3.1053.0",
128
+ "@aws-sdk/credential-provider-node": "3.972.44",
123
129
  "@aws-sdk/lib-storage": "3.1053.0",
124
130
  "@aws-sdk/client-sts": "3.1053.0",
125
131
  "@aws-sdk/signature-v4a": "3.1048.0",
126
132
  "glob": "13.0.6",
133
+ "minimatch": "10.2.5",
127
134
  "node-forge": "1.4.0",
128
135
  "structured-headers": "1.0.1",
129
136
  "tsx": "4.21.0",
130
137
  "typescript": "5.9.3",
131
- "@everystack/model": "0.4.17"
138
+ "@everystack/model": "0.4.18"
132
139
  },
133
140
  "peerDependencies": {
134
141
  "@everystack/server": ">=0.4.0",
@@ -174,6 +181,7 @@
174
181
  "jest": "29.7.0",
175
182
  "react": "19.2.0",
176
183
  "ts-jest": "29.4.9",
184
+ "zod": "3.25.67",
177
185
  "@everystack/server": "0.4.30"
178
186
  },
179
187
  "scripts": {
Binary file
@@ -385,9 +385,11 @@ export interface SequenceGrantContract {
385
385
  /**
386
386
  * The owning column is `GENERATED … AS IDENTITY`, not `serial` — and that changes who needs
387
387
  * what. A serial column's value comes from a DEFAULT `nextval(...)` evaluated with the
388
- * CALLER's privileges, so an INSERTing role must hold USAGE/SELECT/UPDATE on the sequence or
389
- * every insert fails `permission denied for sequence`. An identity column's value is supplied
390
- * by the system as part of the column definition; no caller privilege is consulted at all.
388
+ * CALLER's privileges, so an INSERTing role must hold USAGE on the sequence (all `nextval`
389
+ * needs) or every insert fails `permission denied for sequence`. `SELECT`/`UPDATE` are NOT
390
+ * required for an INSERT — they are administrative (see SEQUENCE_PRIVILEGES_FOR_INSERT). An
391
+ * identity column's value is supplied by the system as part of the column definition; no
392
+ * caller privilege is consulted at all.
391
393
  *
392
394
  * Measured, not assumed: a role with INSERT on the table and NOTHING on the sequence inserts
393
395
  * successfully into an identity table and fails on a serial one. So the A6 inheritance rule
@@ -277,20 +277,23 @@ function tableMap(c: AuthzContract): Map<string, TableContract> {
277
277
  * Measured on a consumer's database: every INSERT into a serial-PK table failed
278
278
  * `permission denied for sequence`, and two of their 563 tests were exactly that.
279
279
  *
280
- * The rule is INSERT on the table → USAGE + SELECT + UPDATE on its sequence, and all three
281
- * matter. USAGE alone permits `nextval`, but `setval` needs UPDATE and reading `last_value`
282
- * needs SELECT, so the common `rU` shape breaks the moment anything reseeds the counter.
280
+ * The rule is INSERT on the table → USAGE on its sequence, and ONLY USAGE. A serial column's
281
+ * value comes from a DEFAULT `nextval(...)`; USAGE is exactly the privilege `nextval` (and
282
+ * `currval`) requires. `setval` (reseed the counter) needs UPDATE and reading `last_value`
283
+ * directly needs SELECT — but neither is part of an INSERT. They are administrative operations,
284
+ * so an INSERT-holder must not inherit them.
283
285
  *
284
- * Signed off after measurement, not assumed: the consumer checked every role against every
285
- * serial/identity sequence and found zero cases of INSERT-without-sequence-USAGE and zero of
286
- * USAGE-without-UPDATE, so this under-grants nobody. Their one divergence — `authenticated`
287
- * holding USAGE on all 27 sequences while able to INSERT into none — was a vestigial 2023
288
- * blanket grant they are revoking, so their database converges to this rule rather than the
289
- * reverse. The pull ADVISORY names that shape wherever it appears.
286
+ * Least privilege, corrected after a regression: an earlier form derived SELECT + UPDATE + USAGE
287
+ * and handed every INSERT-holder `setval` control of a surrogate key. It widened a consumer's
288
+ * governed prod migrator by 28 UPDATE grants nobody asked for — governance ADDED privileges the
289
+ * role had worked fine without. The fix returns to the minimum INSERT needs: USAGE. A role that
290
+ * genuinely reseeds a counter is administrative and is either ungoverned (the reconciler leaves
291
+ * it alone) or must declare the grant deliberately; it is never inferred from INSERT.
290
292
  *
291
- * Derived, never declared: there is no `sequence:` key in the model and nobody wants one.
293
+ * Derived, never declared: there is no `sequence:` key in the model. Declaring sequence UPDATE
294
+ * for a governed role is a deliberately deferred feature — added when a real need names it.
292
295
  */
293
- export const SEQUENCE_PRIVILEGES_FOR_INSERT = ['SELECT', 'UPDATE', 'USAGE'] as const;
296
+ export const SEQUENCE_PRIVILEGES_FOR_INSERT = ['USAGE'] as const;
294
297
 
295
298
  export function inheritedSequenceGrants(table: TableContract): Record<string, string[]> {
296
299
  const out: Record<string, string[]> = {};
@@ -332,10 +335,10 @@ function reconcileSequenceGrants(
332
335
  // NOTHING on the sequence inserts fine into an identity table and fails on a serial one.
333
336
  //
334
337
  // Applying the rule here was a real WIDENING, caught by the round-trip oracle the day
335
- // identity columns became declarable: the rebuild handed an INSERTing role SELECT + UPDATE
336
- // + USAGE — direct `setval` control of a surrogate key — that the original database never
337
- // granted and never needed. Nothing is revoked either: a grant somebody made deliberately
338
- // is not ours to remove, and no declaration says it should not be there.
338
+ // identity columns became declarable: the rebuild handed an INSERTing role USAGE on the
339
+ // identity sequence that the original database never granted and never needed. Nothing is
340
+ // revoked either: a grant somebody made deliberately is not ours to remove, and no
341
+ // declaration says it should not be there.
339
342
  if (seq.identity) continue;
340
343
  const target = quoteQualified(seq.sequence);
341
344
  const grantees = new Set([...Object.keys(declared), ...Object.keys(seq.grants)]);
package/src/cli/aws.ts CHANGED
@@ -290,7 +290,23 @@ export async function resolveFunctionName(
290
290
  * credential-free introspection path. Every deployed-stage read verb
291
291
  * (db:plan, db:apply --stage's descent probe, …) shares it: catalog SQL runs
292
292
  * inside AWS on the operator connection, and the row set comes back over IAM.
293
- * `db:query` refuses writes, so this can only read.
293
+ *
294
+ * WHAT MAKES THIS READ-ONLY: not this file, and no longer a keyword blocklist in the
295
+ * action. `db:query` runs its single statement inside an explicit PostgreSQL `READ ONLY`
296
+ * transaction that always rolls back, so the ENGINE refuses a write — after parsing,
297
+ * where no leading comment, quoted identifier, writable CTE, `SELECT ... INTO`,
298
+ * `EXPLAIN ANALYZE <write>`, `COPY`, `MERGE`, `CALL` or `DO` block reaches. (A red team
299
+ * against a real PostgreSQL 16 defeated the blocklist that stood here before; see the
300
+ * comment on the `db:query` action in `@everystack/server`'s plugin.ts.)
301
+ *
302
+ * WHAT IT DOES NOT COVER: a read-only transaction stops DATA writes, not every effect.
303
+ * Still reachable by anyone who may invoke this action — `CLUSTER`, `REINDEX`,
304
+ * `LOCK TABLE ... ACCESS EXCLUSIVE`, `pg_sleep`, `NOTIFY`, and session advisory locks:
305
+ * availability and side-channel effects, not data change. An out-of-transaction escape
306
+ * exists via `dblink_exec` / `pg_background` IF those extensions are installed AND
307
+ * executable by the operator role. Closing those needs a real SELECT-only role wired in
308
+ * as the query connection (`readDatabaseUrl`) — a separate, already-filed follow-up, and
309
+ * an infrastructure decision rather than a code change here.
294
310
  */
295
311
  export function lambdaQueryRunner(
296
312
  region: string,