@everystack/server 0.4.31 → 0.4.33
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 +17 -0
- package/package.json +3 -3
- package/src/owner-enactment.ts +29 -5
- package/src/provision.ts +152 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,23 @@ All notable changes to `@everystack/server` are documented here.
|
|
|
4
4
|
|
|
5
5
|
Format based on [Keep a Changelog](https://keepachangelog.com/).
|
|
6
6
|
|
|
7
|
+
## [0.4.33] - 2026-09-24
|
|
8
|
+
|
|
9
|
+
### BREAKING CHANGES
|
|
10
|
+
|
|
11
|
+
- a declared owner needs USAGE on its schema; db:apply refuses without it, db:provision (server 0.4.33) grants it
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- name USAGE with CREATE for a missing schema; review follow-ups
|
|
16
|
+
- reject offset-less datetime strings (ambiguous: server-local, not UTC)
|
|
17
|
+
|
|
18
|
+
## [0.4.32] - 2026-09-21
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- db:provision detects PUBLIC's CREATE on schema public, revokes it under --confirm
|
|
23
|
+
|
|
7
24
|
## [0.4.31] - 2026-09-16
|
|
8
25
|
|
|
9
26
|
### Added
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@everystack/server",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.33",
|
|
4
4
|
"description": "Server runtime primitives for Lambda — event adapters, routing, SSR, image processing",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"author": "Scalable Technology, Inc. <licensing@scalable.technology>",
|
|
@@ -187,8 +187,8 @@
|
|
|
187
187
|
"sst": "4.13.1",
|
|
188
188
|
"ts-jest": "29.4.9",
|
|
189
189
|
"typescript": "5.9.3",
|
|
190
|
-
"@everystack/
|
|
191
|
-
"@everystack/
|
|
190
|
+
"@everystack/auth": "0.4.6",
|
|
191
|
+
"@everystack/cli": "0.4.93"
|
|
192
192
|
},
|
|
193
193
|
"scripts": {
|
|
194
194
|
"test": "jest",
|
package/src/owner-enactment.ts
CHANGED
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
* explicit SET ROLE, never inheriting the owner's privileges ambiently.
|
|
12
12
|
* 2. `GRANT CREATE ON SCHEMA <schema> TO <owner>` — so the CREATE, which runs AS the owner,
|
|
13
13
|
* can place the object.
|
|
14
|
+
* 3. `GRANT USAGE ON SCHEMA <schema> TO <owner>` — so the owner's GRANT/REVOKE on that object,
|
|
15
|
+
* which name it as `schema.object`, can resolve it. CREATE alone let the CREATE through and
|
|
16
|
+
* then failed the object's own `REVOKE ALL … FROM PUBLIC` with `permission denied for schema`.
|
|
14
17
|
*
|
|
15
18
|
* These are privileged, idempotent SETUP — the same category as the role chain — so they belong
|
|
16
19
|
* in db:provision, not in the least-privilege reconcile lane (which structurally cannot run them).
|
|
@@ -75,6 +78,8 @@ export interface OwnerFactRow {
|
|
|
75
78
|
canSetRole: boolean;
|
|
76
79
|
/** The owner already holds `CREATE` on the schema — no create grant needed. */
|
|
77
80
|
ownerCanCreate: boolean;
|
|
81
|
+
/** The owner already holds `USAGE` on the schema — no usage grant needed. */
|
|
82
|
+
ownerCanUse: boolean;
|
|
78
83
|
}
|
|
79
84
|
|
|
80
85
|
/**
|
|
@@ -88,6 +93,8 @@ export interface GranterCapabilityRow {
|
|
|
88
93
|
schema: string;
|
|
89
94
|
canGrantMembership: boolean;
|
|
90
95
|
canGrantCreate: boolean;
|
|
96
|
+
/** Can `GRANT USAGE ON SCHEMA` — owns the schema, holds USAGE WITH GRANT OPTION, or is a superuser. */
|
|
97
|
+
canGrantUsage: boolean;
|
|
91
98
|
}
|
|
92
99
|
|
|
93
100
|
/** Which connection runs a unit's statements. */
|
|
@@ -175,7 +182,8 @@ function assertIdent(name: string, what: string): void {
|
|
|
175
182
|
|
|
176
183
|
/**
|
|
177
184
|
* The connection-independent probe: for each (owner, schema), does the owner exist, does the
|
|
178
|
-
* schema exist, can `operator` already SET ROLE the owner, and does the owner already hold CREATE
|
|
185
|
+
* schema exist, can `operator` already SET ROLE the owner, and does the owner already hold CREATE
|
|
186
|
+
* and USAGE.
|
|
179
187
|
* `has_schema_privilege` throws on an absent schema, so the schema is LEFT JOINed and the privilege
|
|
180
188
|
* is evaluated only when the schema is really there. Run once, on either connection.
|
|
181
189
|
*/
|
|
@@ -195,7 +203,9 @@ SELECT
|
|
|
195
203
|
(n.oid IS NOT NULL) AS schema_exists,
|
|
196
204
|
CASE WHEN r.oid IS NULL THEN false ELSE pg_has_role('${operator}', r.oid, 'SET') END AS can_set_role,
|
|
197
205
|
CASE WHEN r.oid IS NULL OR n.oid IS NULL THEN false
|
|
198
|
-
ELSE has_schema_privilege(r.oid, n.oid, 'CREATE') END AS owner_can_create
|
|
206
|
+
ELSE has_schema_privilege(r.oid, n.oid, 'CREATE') END AS owner_can_create,
|
|
207
|
+
CASE WHEN r.oid IS NULL OR n.oid IS NULL THEN false
|
|
208
|
+
ELSE has_schema_privilege(r.oid, n.oid, 'USAGE') END AS owner_can_use
|
|
199
209
|
FROM declared d
|
|
200
210
|
LEFT JOIN pg_roles r ON r.rolname = d.owner_name
|
|
201
211
|
LEFT JOIN pg_namespace n ON n.nspname = d.schema_name
|
|
@@ -207,7 +217,8 @@ ORDER BY d.owner_name, d.schema_name
|
|
|
207
217
|
* The connection-DEPENDENT probe: run on a connection to learn what CURRENT_USER can grant.
|
|
208
218
|
* Can it `GRANT <owner> TO <operator>` — it holds ADMIN OPTION on the owner, directly or through a
|
|
209
219
|
* group. Can it `GRANT CREATE ON SCHEMA <schema>` — it owns the schema or holds CREATE WITH GRANT
|
|
210
|
-
* OPTION
|
|
220
|
+
* OPTION, and `GRANT USAGE ON SCHEMA` — it owns the schema or holds USAGE WITH GRANT OPTION. A REAL
|
|
221
|
+
* superuser can do all three unconditionally. Run on the operator's connection and, when
|
|
211
222
|
* present, on the master's; the router merges the two.
|
|
212
223
|
*
|
|
213
224
|
* MEMBERSHIP IN `rds_superuser` IS NOT SUPERUSER, and this probe no longer pretends it is. RDS's
|
|
@@ -238,7 +249,9 @@ SELECT
|
|
|
238
249
|
d.schema_name AS schema,
|
|
239
250
|
COALESCE(me.is_super OR pg_has_role(me.oid, r.oid, 'MEMBER WITH ADMIN OPTION'), false) AS can_grant_membership,
|
|
240
251
|
COALESCE(me.is_super OR n.nspowner = me.oid
|
|
241
|
-
OR has_schema_privilege(me.oid, n.oid, 'CREATE WITH GRANT OPTION'), false) AS can_grant_create
|
|
252
|
+
OR has_schema_privilege(me.oid, n.oid, 'CREATE WITH GRANT OPTION'), false) AS can_grant_create,
|
|
253
|
+
COALESCE(me.is_super OR n.nspowner = me.oid
|
|
254
|
+
OR has_schema_privilege(me.oid, n.oid, 'USAGE WITH GRANT OPTION'), false) AS can_grant_usage
|
|
242
255
|
FROM declared d
|
|
243
256
|
CROSS JOIN me
|
|
244
257
|
LEFT JOIN pg_roles r ON r.rolname = d.owner_name
|
|
@@ -250,7 +263,8 @@ ORDER BY d.owner_name, d.schema_name
|
|
|
250
263
|
const keyOf = (owner: string, schema: string) => `${owner}\u0000${schema}`;
|
|
251
264
|
|
|
252
265
|
/**
|
|
253
|
-
* Route the
|
|
266
|
+
* Route the three grants each (owner, schema) may need — membership, CREATE, USAGE — onto the
|
|
267
|
+
* connection that can run them.
|
|
254
268
|
*
|
|
255
269
|
* `operator` is the least-privilege role (`migrator`). `masterCaps` is null when no master
|
|
256
270
|
* connection is available — then anything the operator cannot do is a refusal, never an
|
|
@@ -305,6 +319,16 @@ export function planOwnerEnactment(
|
|
|
305
319
|
`Run as the schema owner: ${stmt};`,
|
|
306
320
|
);
|
|
307
321
|
}
|
|
322
|
+
// 3. Usage: the owner's GRANT/REVOKE on the object name `schema.object` — resolving it needs USAGE.
|
|
323
|
+
if (f.schemaExists && !f.ownerCanUse) {
|
|
324
|
+
const stmt = `GRANT USAGE ON SCHEMA ${f.schema} TO ${f.owner}`;
|
|
325
|
+
if (op?.canGrantUsage) addUnit(b, subject, 'operator', [stmt]);
|
|
326
|
+
else if (master?.canGrantUsage) addUnit(b, subject, 'master', [stmt]);
|
|
327
|
+
else plan.refusals.push(
|
|
328
|
+
`cannot GRANT USAGE ON SCHEMA ${f.schema} TO ${f.owner}: neither connection owns the schema or holds USAGE WITH GRANT OPTION. ` +
|
|
329
|
+
`Run as the schema owner: ${stmt};`,
|
|
330
|
+
);
|
|
331
|
+
}
|
|
308
332
|
// An absent schema is not enacted here — it is created by the reconcile builder, whose own
|
|
309
333
|
// preflight then names the owner's missing CREATE. Enactment acts only on what exists.
|
|
310
334
|
}
|
package/src/provision.ts
CHANGED
|
@@ -196,9 +196,25 @@ export interface ProvisionResult {
|
|
|
196
196
|
* anything and never throw — a reader that cannot read is loud, recoverable, and reported.
|
|
197
197
|
*/
|
|
198
198
|
readerGrants?: { granted: number; refusals: string[] };
|
|
199
|
+
/**
|
|
200
|
+
* The PUBLIC-on-schema-`public` posture, and what (if anything) was done about it. Always present
|
|
201
|
+
* once convergence has run — `publicCanCreate: false` is the already-clean answer, and the CLI
|
|
202
|
+
* says nothing about it. Same non-fatal contract as ENACT: never throws, never aborts.
|
|
203
|
+
*/
|
|
204
|
+
publicSchemaPosture?: PublicSchemaPosture;
|
|
199
205
|
error?: string;
|
|
200
206
|
}
|
|
201
207
|
|
|
208
|
+
/** The result of the PUBLIC-on-`public` posture step. See `convergePublicSchemaPosture`. */
|
|
209
|
+
export interface PublicSchemaPosture {
|
|
210
|
+
/** Does the PUBLIC pseudo-role hold CREATE on schema `public`? (The pre-PG15 default.) */
|
|
211
|
+
publicCanCreate: boolean;
|
|
212
|
+
/** Was the REVOKE applied AND verified? Only ever true when `publicCanCreate` was true. */
|
|
213
|
+
revoked: boolean;
|
|
214
|
+
/** Why it was not applied — the missing `--confirm`, a connection that cannot, a server refusal. */
|
|
215
|
+
refusals: string[];
|
|
216
|
+
}
|
|
217
|
+
|
|
202
218
|
const ALNUM = /^[A-Za-z0-9]+$/;
|
|
203
219
|
|
|
204
220
|
export async function runProvision(
|
|
@@ -384,8 +400,8 @@ export async function runProvision(
|
|
|
384
400
|
export async function convergeOwnership(
|
|
385
401
|
payload: Pick<ProvisionPayload, 'declaredOwners' | 'declaredObjects' | 'confirm' | 'adminRole' | 'readerRole' | 'searchPath'>,
|
|
386
402
|
deps: ProvisionDeps,
|
|
387
|
-
): Promise<Pick<ProvisionResult, 'ownerEnactment' | 'ownerAdoption' | 'readerGrants'>> {
|
|
388
|
-
const out: Pick<ProvisionResult, 'ownerEnactment' | 'ownerAdoption' | 'readerGrants'> = {};
|
|
403
|
+
): Promise<Pick<ProvisionResult, 'ownerEnactment' | 'ownerAdoption' | 'readerGrants' | 'publicSchemaPosture'>> {
|
|
404
|
+
const out: Pick<ProvisionResult, 'ownerEnactment' | 'ownerAdoption' | 'readerGrants' | 'publicSchemaPosture'> = {};
|
|
389
405
|
if (!deps.query) return out;
|
|
390
406
|
const operator = payload.adminRole ?? 'migrator';
|
|
391
407
|
|
|
@@ -416,11 +432,143 @@ export async function convergeOwnership(
|
|
|
416
432
|
out.readerGrants = { granted: 0, refusals: [`reader grants failed (credentials are already saved; nothing is stranded): ${err?.message ?? String(err)}`] };
|
|
417
433
|
}
|
|
418
434
|
}
|
|
435
|
+
|
|
436
|
+
// The PUBLIC-on-`public` posture — LAST, and UNCONDITIONAL. It is a property of the DATABASE, not
|
|
437
|
+
// of what the Models declare, so it must not hide behind the declared-ownership guards above: the
|
|
438
|
+
// brownfield app with nothing declared is precisely the one most likely to carry the pre-PG15
|
|
439
|
+
// grant. One catalog read on a converged database, one statement when the operator has confirmed.
|
|
440
|
+
try {
|
|
441
|
+
out.publicSchemaPosture = await convergePublicSchemaPosture(deps, !!payload.confirm);
|
|
442
|
+
} catch (err: any) {
|
|
443
|
+
out.publicSchemaPosture = { publicCanCreate: false, revoked: false, refusals: [
|
|
444
|
+
`the schema "public" posture check failed (credentials are already saved; nothing is stranded): ${err?.message ?? String(err)}`,
|
|
445
|
+
] };
|
|
446
|
+
}
|
|
419
447
|
return out;
|
|
420
448
|
}
|
|
421
449
|
|
|
422
450
|
const truthy = (v: unknown): boolean => v === true || v === 't' || v === 'true';
|
|
423
451
|
|
|
452
|
+
/** The one statement this step ever sends. Also the fix SQL every refusal below hands back. */
|
|
453
|
+
export const REVOKE_PUBLIC_CREATE_SQL = 'REVOKE CREATE ON SCHEMA public FROM PUBLIC';
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* The posture probe: does the PUBLIC pseudo-role hold CREATE on schema `public`, and can THIS
|
|
457
|
+
* connection take it away?
|
|
458
|
+
*
|
|
459
|
+
* `public_can_create` reads the ACL directly rather than asking `has_schema_privilege('public', …)`.
|
|
460
|
+
* Both answer correctly on a live server (measured, PG16), but the ACL read is the idiom the rest of
|
|
461
|
+
* this file already uses (`toFacts`/`toCaps` over `aclexplode`) and it is unambiguous: grantee 0 IS
|
|
462
|
+
* the PUBLIC pseudo-role, whereas the literal `'public'` in `has_schema_privilege` is a special-cased
|
|
463
|
+
* NAME that resolves to no row in `pg_roles`. A NULL `nspacl` (owner-only defaults, and what a
|
|
464
|
+
* hand-recreated `public` schema carries on every version) explodes to zero rows — correctly, no grant.
|
|
465
|
+
*
|
|
466
|
+
* This is deliberately NOT `server_version_num`. PG15 removed the grant as a DEFAULT; it says nothing
|
|
467
|
+
* about the database in front of us. A PG16 database restored from a pre-15 dump still carries it, and
|
|
468
|
+
* an operator can re-add it on any version with one legal statement. The catalog is the only truth.
|
|
469
|
+
*
|
|
470
|
+
* `can_revoke` is the capability half, and it is load-bearing for a reason PostgreSQL makes easy to
|
|
471
|
+
* miss: a REVOKE run by a role that does not administer the schema DOES NOT FAIL. It emits
|
|
472
|
+
* `WARNING: no privileges could be revoked for "public"` and returns success with the grant still in
|
|
473
|
+
* place. `pg_has_role(current_user, nspowner, 'USAGE')` is true for the schema's owner, for a member
|
|
474
|
+
* of it, and for a superuser — exactly the set that can actually revoke.
|
|
475
|
+
*/
|
|
476
|
+
export const PUBLIC_SCHEMA_POSTURE_SQL = `SELECT
|
|
477
|
+
EXISTS (
|
|
478
|
+
SELECT 1 FROM pg_namespace n, aclexplode(n.nspacl) a
|
|
479
|
+
WHERE n.nspname = 'public' AND a.grantee = 0 AND a.privilege_type = 'CREATE'
|
|
480
|
+
) AS public_can_create,
|
|
481
|
+
EXISTS (
|
|
482
|
+
SELECT 1 FROM pg_namespace n
|
|
483
|
+
WHERE n.nspname = 'public' AND pg_has_role(current_user, n.nspowner, 'USAGE')
|
|
484
|
+
) AS can_revoke`;
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Converge the PUBLIC-on-schema-`public` posture — detect always, act only under `confirm`.
|
|
488
|
+
*
|
|
489
|
+
* THE GAP. Before PG15, `CREATE ON SCHEMA public` was granted to the PUBLIC pseudo-role by default.
|
|
490
|
+
* PG15 removed that default. Where it is still in place, every login this file provisions —
|
|
491
|
+
* authenticator, migrator, reader — can create objects in `public` through an ambient grant none of
|
|
492
|
+
* them was given, whatever its own privileges say. A schema anyone can write is also a search_path
|
|
493
|
+
* hijack surface: plant a shadow object there and a privileged session resolving through `public`
|
|
494
|
+
* runs it (the same chain `security-audit` reds on).
|
|
495
|
+
*
|
|
496
|
+
* WHY THIS IS GATED, when db:build revokes the same grant unconditionally. db:build refuses a
|
|
497
|
+
* non-empty target, so there is nothing in `public` to strand and no app to surprise. db:provision
|
|
498
|
+
* has no such guarantee: it runs against a LIVE, often long-running database, and some legacy apps
|
|
499
|
+
* genuinely create objects in `public` at runtime. Taking that away silently, mid-deploy, is a
|
|
500
|
+
* behaviour change no one asked for. So this reports by default and acts only when the operator has
|
|
501
|
+
* seen the report and re-run with `--confirm` — the same shape ADOPT uses for its runtime-changing
|
|
502
|
+
* reassignments.
|
|
503
|
+
*
|
|
504
|
+
* ROUTED like ENACT's grants: the operator's own connection when it administers `public`, the master
|
|
505
|
+
* when it does not, and a refusal carrying the runnable SQL when neither can. VERIFIED after the
|
|
506
|
+
* fact, because — see PUBLIC_SCHEMA_POSTURE_SQL — an under-privileged REVOKE reports success while
|
|
507
|
+
* changing nothing, so "the statement ran" would have printed `revoked` over a wide-open database.
|
|
508
|
+
*
|
|
509
|
+
* Non-fatal by construction: it never throws. It runs strictly after the credential is in the secret
|
|
510
|
+
* store, so anything it fails to do is recoverable by a re-run and strands nothing.
|
|
511
|
+
*/
|
|
512
|
+
export async function convergePublicSchemaPosture(
|
|
513
|
+
deps: ProvisionDeps,
|
|
514
|
+
confirm: boolean,
|
|
515
|
+
): Promise<PublicSchemaPosture> {
|
|
516
|
+
const query = deps.query;
|
|
517
|
+
if (!query) return { publicCanCreate: false, revoked: false, refusals: [] };
|
|
518
|
+
|
|
519
|
+
const [row] = await query(PUBLIC_SCHEMA_POSTURE_SQL);
|
|
520
|
+
if (!truthy(row?.public_can_create)) return { publicCanCreate: false, revoked: false, refusals: [] };
|
|
521
|
+
|
|
522
|
+
if (!confirm) {
|
|
523
|
+
return { publicCanCreate: true, revoked: false, refusals: [
|
|
524
|
+
'the PUBLIC pseudo-role holds CREATE on schema "public" — the pre-PG15 default, which PG15 ' +
|
|
525
|
+
'removed. Every login this provision created can place objects there through that ambient ' +
|
|
526
|
+
'grant, and a writable schema on a search path is a hijack surface for every privileged ' +
|
|
527
|
+
'session resolving through it. Taking it away can break an app that creates objects in ' +
|
|
528
|
+
'"public" at runtime, so it is NOT done silently: review, then re-run with --confirm. ' +
|
|
529
|
+
`The statement it would send: ${REVOKE_PUBLIC_CREATE_SQL};`,
|
|
530
|
+
] };
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Route to a connection that can actually revoke. The operator usually does not own `public`
|
|
534
|
+
// (postgres or pg_database_owner does), so the master is the normal venue.
|
|
535
|
+
let execute: ((sql: string) => Promise<unknown>) | null = null;
|
|
536
|
+
if (truthy(row?.can_revoke)) {
|
|
537
|
+
execute = deps.execute;
|
|
538
|
+
} else if (deps.master) {
|
|
539
|
+
const [masterRow] = await deps.master.query(PUBLIC_SCHEMA_POSTURE_SQL);
|
|
540
|
+
if (truthy(masterRow?.can_revoke)) execute = (s: string) => deps.master!.execute(s);
|
|
541
|
+
}
|
|
542
|
+
if (!execute) {
|
|
543
|
+
return { publicCanCreate: true, revoked: false, refusals: [
|
|
544
|
+
'cannot revoke PUBLIC\'s CREATE on schema "public": neither the operator connection nor the ' +
|
|
545
|
+
'master is the owner of schema "public" (nor a member of its owner). PostgreSQL would accept ' +
|
|
546
|
+
'the statement from them and change NOTHING — it warns "no privileges could be revoked" and ' +
|
|
547
|
+
`returns success. Run it as the schema's owner: ${REVOKE_PUBLIC_CREATE_SQL};`,
|
|
548
|
+
] };
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
try {
|
|
552
|
+
await execute(`${REVOKE_PUBLIC_CREATE_SQL};`);
|
|
553
|
+
} catch (err: any) {
|
|
554
|
+
return { publicCanCreate: true, revoked: false, refusals: [
|
|
555
|
+
`revoking PUBLIC's CREATE on schema "public" failed: ${err?.message ?? String(err)}. ` +
|
|
556
|
+
`Nothing else is affected — credentials are already saved. Fix: ${REVOKE_PUBLIC_CREATE_SQL};`,
|
|
557
|
+
] };
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// VERIFY. The statement completing is not evidence (see above), so re-read the catalog.
|
|
561
|
+
const [after] = await query(PUBLIC_SCHEMA_POSTURE_SQL);
|
|
562
|
+
if (truthy(after?.public_can_create)) {
|
|
563
|
+
return { publicCanCreate: true, revoked: false, refusals: [
|
|
564
|
+
'the REVOKE was accepted but PUBLIC still holds CREATE on schema "public". PostgreSQL does ' +
|
|
565
|
+
'not error when a role that does not administer the schema revokes — it warns and changes ' +
|
|
566
|
+
`nothing. Run it as the schema's owner: ${REVOKE_PUBLIC_CREATE_SQL};`,
|
|
567
|
+
] };
|
|
568
|
+
}
|
|
569
|
+
return { publicCanCreate: true, revoked: true, refusals: [] };
|
|
570
|
+
}
|
|
571
|
+
|
|
424
572
|
/**
|
|
425
573
|
* Run `statements` as ONE transaction on `execute`, and leave the SESSION usable whatever happens.
|
|
426
574
|
*
|
|
@@ -458,6 +606,7 @@ function toFacts(rows: any[]): OwnerFactRow[] {
|
|
|
458
606
|
schemaExists: truthy(r.schema_exists),
|
|
459
607
|
canSetRole: truthy(r.can_set_role),
|
|
460
608
|
ownerCanCreate: truthy(r.owner_can_create),
|
|
609
|
+
ownerCanUse: truthy(r.owner_can_use),
|
|
461
610
|
}));
|
|
462
611
|
}
|
|
463
612
|
|
|
@@ -467,6 +616,7 @@ function toCaps(rows: any[]): GranterCapabilityRow[] {
|
|
|
467
616
|
schema: String(r.schema),
|
|
468
617
|
canGrantMembership: truthy(r.can_grant_membership),
|
|
469
618
|
canGrantCreate: truthy(r.can_grant_create),
|
|
619
|
+
canGrantUsage: truthy(r.can_grant_usage),
|
|
470
620
|
}));
|
|
471
621
|
}
|
|
472
622
|
|