@everystack/cli 0.4.65 → 0.4.66

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@everystack/cli",
3
- "version": "0.4.65",
3
+ "version": "0.4.66",
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>",
@@ -126,7 +126,7 @@
126
126
  "structured-headers": "1.0.1",
127
127
  "tsx": "4.21.0",
128
128
  "typescript": "5.9.3",
129
- "@everystack/model": "0.4.16"
129
+ "@everystack/model": "0.4.17"
130
130
  },
131
131
  "peerDependencies": {
132
132
  "@everystack/server": ">=0.4.0",
@@ -331,54 +331,53 @@ export function compileTableContract(model: ModelDescriptor, opts: CompileOption
331
331
  const isRoleRead = (a: Ability): boolean =>
332
332
  a.action === 'read' && Boolean(a.condition.role) && !isColumnAbility(a);
333
333
 
334
- // Author-supplied predicates, per action. `manage` carries no predicate of its own —
335
- // it is the admin bypass so only the specific verbs are consulted.
334
+ // `manage` + owner/via desugars into four verb abilities for policy emission.
335
+ // Role-scoped `manage` (the admin bypass) passes through it matches `hasAdminManage`
336
+ // directly. Grants handle `manage` natively via `verbsFor`, so this expansion is
337
+ // policy-compiler-local: `compileGrants` still reads `model.abilities`.
338
+ const abilities: readonly Ability[] = model.abilities.flatMap((a) => {
339
+ if (a.action !== 'manage' || !rowScoped(a)) return [a];
340
+ const c = a.condition;
341
+ return [
342
+ { action: 'read' as const, condition: c },
343
+ { action: 'create' as const, condition: c },
344
+ { action: 'update' as const, condition: c },
345
+ { action: 'delete' as const, condition: c },
346
+ ];
347
+ });
348
+
336
349
  const predFor = (action: Ability['action']): string | null => {
337
- for (const a of model.abilities) {
350
+ for (const a of abilities) {
338
351
  if (a.action !== action || isRoleRead(a) || isColumnAbility(a)) continue;
339
352
  const p = rawPredicate(a.condition.sql ?? a.condition.where, `${table}: can('${action}')`);
340
353
  if (p) return p;
341
354
  }
342
355
  return null;
343
356
  };
344
- /**
345
- * The predicate on the PUBLIC read — a read ability with neither a role nor a row scope.
346
- *
347
- * Read separately from {@link ownerReadPred} because the two are different BRANCHES of
348
- * the same authenticated policy. Taking both from one ordered lookup made the anon
349
- * policy depend on the order the abilities happened to be declared in: put the owner
350
- * read first and `anon` inherited the owner branch's guard as its whole public rule.
351
- */
352
357
  const publicReadPred = (): string | null => {
353
- for (const a of model.abilities) {
358
+ for (const a of abilities) {
354
359
  if (a.action !== 'read' || a.condition.role || rowScoped(a)) continue;
355
360
  const p = rawPredicate(a.condition.sql ?? a.condition.where, `${table}: can('read')`);
356
361
  if (p) return p;
357
362
  }
358
363
  return null;
359
364
  };
360
-
361
- /** The predicate the OWNER read narrows ITSELF by — `can('read', { owner, sql })`. */
362
365
  const ownerReadPred = (): string | null => {
363
- for (const a of model.abilities) {
366
+ for (const a of abilities) {
364
367
  if (a.action !== 'read' || !rowScoped(a) || isColumnAbility(a)) continue;
365
368
  const p = rawPredicate(a.condition.sql ?? a.condition.where, `${table}: can('read', { owner })`);
366
369
  if (p) return p;
367
370
  }
368
371
  return null;
369
372
  };
370
-
371
- /** The WRITE half, when the author declared one distinct from the read half. */
372
373
  const checkFor = (action: Ability['action']): string | null => {
373
- for (const a of model.abilities) {
374
+ for (const a of abilities) {
374
375
  if (a.action !== action) continue;
375
376
  const p = rawPredicate(a.condition.check, `${table}: can('${action}', { check })`);
376
377
  if (p) return p;
377
378
  }
378
379
  return null;
379
380
  };
380
-
381
- const abilities = model.abilities;
382
381
  const hasAdminManage = abilities.some((a) => a.action === 'manage' && a.condition.role === 'admin');
383
382
  const hasPublicRead = abilities.some((a) => a.action === 'read' && !a.condition.role && !rowScoped(a));
384
383
  // The audience is open when the public read says so. Read off the SAME abilities that make
@@ -569,8 +568,8 @@ export function compileTableContract(model: ModelDescriptor, opts: CompileOption
569
568
  // key, and undefined must mean ENABLED — the pre-flag behavior — never a silent
570
569
  // security downgrade via version skew.
571
570
  rls: { enabled: model.rls !== false, forced: model.rls !== false && model.writtenBy === 'app' },
572
- grants: compileGrants(abilities, model.privileges),
573
- ...(compileColumnGrants(abilities) ? { columnGrants: compileColumnGrants(abilities) } : {}),
571
+ grants: compileGrants(model.abilities, model.privileges),
572
+ ...(compileColumnGrants(model.abilities) ? { columnGrants: compileColumnGrants(model.abilities) } : {}),
574
573
  policies,
575
574
  };
576
575
  }
@@ -189,6 +189,7 @@ export function findSecdefExecuteGaps(derived: readonly DerivedDescriptor[]): De
189
189
  for (const d of derived) {
190
190
  if (d.kind !== 'function' || d.security !== 'definer' || d.abilities.length > 0) continue;
191
191
  if (d.returns === 'trigger') continue;
192
+ if (d.internal) continue;
192
193
  if (Object.keys(d.privileges ?? {}).length > 0) continue;
193
194
  const identity = identityOf(d);
194
195
  gaps.push({