@nodii/db-rls 0.2.2 → 0.4.0
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/configure.d.ts +42 -8
- package/dist/configure.d.ts.map +1 -1
- package/dist/configure.js +87 -16
- package/dist/configure.js.map +1 -1
- package/dist/context.d.ts +36 -7
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +67 -6
- package/dist/context.js.map +1 -1
- package/dist/drizzle-client.d.ts +9 -2
- package/dist/drizzle-client.d.ts.map +1 -1
- package/dist/drizzle-client.js +28 -2
- package/dist/drizzle-client.js.map +1 -1
- package/dist/drizzle-grants.d.ts +203 -0
- package/dist/drizzle-grants.d.ts.map +1 -0
- package/dist/drizzle-grants.js +68 -0
- package/dist/drizzle-grants.js.map +1 -0
- package/dist/drizzle-policies.d.ts +90 -0
- package/dist/drizzle-policies.d.ts.map +1 -0
- package/dist/drizzle-policies.js +223 -0
- package/dist/drizzle-policies.js.map +1 -0
- package/dist/index.d.ts +9 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -10
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +23 -5
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +38 -4
- package/dist/init.js.map +1 -1
- package/dist/metrics.d.ts +28 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +263 -0
- package/dist/metrics.js.map +1 -0
- package/dist/migrate-guard.d.ts +14 -0
- package/dist/migrate-guard.d.ts.map +1 -0
- package/dist/migrate-guard.js +33 -0
- package/dist/migrate-guard.js.map +1 -0
- package/dist/pg-pool-adapter.d.ts.map +1 -1
- package/dist/pg-pool-adapter.js +78 -25
- package/dist/pg-pool-adapter.js.map +1 -1
- package/dist/postgres-js-client.d.ts +60 -0
- package/dist/postgres-js-client.d.ts.map +1 -0
- package/dist/postgres-js-client.js +223 -0
- package/dist/postgres-js-client.js.map +1 -0
- package/dist/types.d.ts +68 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +31 -1
- package/dist/types.js.map +1 -1
- package/package.json +24 -5
package/dist/index.js
CHANGED
|
@@ -1,32 +1,43 @@
|
|
|
1
|
-
// @nodii/db-rls — v0.
|
|
1
|
+
// @nodii/db-rls — v0.2.3 public barrel.
|
|
2
2
|
//
|
|
3
3
|
// Spec: planning hub feature_doc serviceId=nodii-libs docKey=db-rls
|
|
4
|
-
// (A1 posture — RLS on every
|
|
4
|
+
// + global doctrine `08-rls-doctrine.md` (A1 posture — RLS on every
|
|
5
|
+
// tenant-scoped table — operator-confirmed 2026-06-02).
|
|
5
6
|
// Polyglot ship: TS + Python + Go in parity.
|
|
6
7
|
//
|
|
7
|
-
// v0.1.1
|
|
8
|
-
// - configureDbRls (
|
|
8
|
+
// v0.2.3 surface (additive over v0.1.1):
|
|
9
|
+
// - configureDbRls (3-pool) + configureDbRls2Pool (2-pool exception
|
|
10
|
+
// per 08-rls § 7) + initDbRls (raw)
|
|
9
11
|
// - PgClientPool (wraps pg.Pool → PgPool interface)
|
|
10
12
|
// - InMemoryPgPool (still exported — used by upstream tests)
|
|
11
13
|
// - withUserContext / withSystemContext / withOwnerContext / autoDispatch
|
|
12
14
|
// (operates on the substrate-agnostic PgTx)
|
|
13
15
|
// - withUserContextDb / withSystemContextDb / withOwnerContextDb
|
|
14
16
|
// (Drizzle-bound; require a real PgClientPool)
|
|
17
|
+
// - withOwnerContext now enforces NODII_ENV=migrate per 08-rls § 10.2
|
|
15
18
|
// - applyAuthSchemaMigrations + AUTH_SCHEMA_SQL + AUTH_SCHEMA_GRANTS_SQL
|
|
16
|
-
// - withRls() (
|
|
19
|
+
// - withRls() (legacy DDL-string generator per § 5.5; keeps *_policy suffix)
|
|
20
|
+
// - withRls() Drizzle table-builder (subpath `@nodii/db-rls/drizzle`)
|
|
21
|
+
// per doctrine § 4.2 — emits canonical 5-policy DDL via Drizzle's
|
|
22
|
+
// pgPolicy constructs, naming follows doctrine § 4 (no `_policy` suffix)
|
|
23
|
+
// - postgres-js adapter (subpath `@nodii/db-rls/postgres-js`) — nodii-auth
|
|
24
|
+
// and other postgres-js consumers
|
|
25
|
+
// - DB_RLS_METRIC_VOCABULARY + 9 locked metrics + 2 spans per § 5.10
|
|
17
26
|
// - generateRlsPolicy / authSchemaHelpers (deprecated aliases, kept for
|
|
18
27
|
// parity-fence + parity with the v0.1.0 surface; no NotImplemented paths)
|
|
19
28
|
// - migrateGen + runMigrateGenCli + userTenantIdsRealSql + userTenantIdsStubSql
|
|
20
29
|
// - Typed errors: DbRlsError + DbRlsNotInitialized + PoolUnavailable +
|
|
21
30
|
// MissingTenantContext + TransactionRolledBack +
|
|
22
|
-
// PoolCredentialMismatch
|
|
31
|
+
// PoolCredentialMismatch + OwnerContextOutsideMigrate +
|
|
32
|
+
// DbRlsConfigError
|
|
23
33
|
export const LIB_NAME = "db-rls";
|
|
24
|
-
export const VERSION = "0.
|
|
34
|
+
export const VERSION = "0.4.0";
|
|
25
35
|
export { initDbRls, requireDbRls, getDbRlsOrNull, requirePool, _resetForTests, } from "./init";
|
|
26
|
-
export { configureDbRls, PoolCredentialMismatch, _resetConfigureForTests, } from "./configure";
|
|
36
|
+
export { configureDbRls, configureDbRls2Pool, configureDbRls4Pool, PoolCredentialMismatch, _resetConfigureForTests, } from "./configure";
|
|
27
37
|
export { PgClientPool } from "./pg-pool-adapter";
|
|
28
|
-
export { withUserContext, withSystemContext, withOwnerContext, autoDispatch, } from "./context";
|
|
38
|
+
export { withUserContext, withSystemContext, withOwnerContext, withPlatformOps, autoDispatch, } from "./context";
|
|
29
39
|
export { withUserContextDb, withSystemContextDb, withOwnerContextDb, } from "./drizzle-client";
|
|
40
|
+
export { assertMigrateAllowed } from "./migrate-guard";
|
|
30
41
|
export { AUTH_SCHEMA_SQL, AUTH_SCHEMA_GRANTS_SQL, applyAuthSchemaMigrations, authSchemaFullSql, } from "./auth-schema";
|
|
31
42
|
export { withRls, generateTableGrants } from "./rls-policy-gen";
|
|
32
43
|
export { migrateGen, runMigrateGenCli, userTenantIdsRealSql, userTenantIdsStubSql, } from "./migrate-gen";
|
|
@@ -34,5 +45,6 @@ export { migrateGen, runMigrateGenCli, userTenantIdsRealSql, userTenantIdsStubSq
|
|
|
34
45
|
// names. Both are byte-equal SQL helpers — no NotImplemented paths.
|
|
35
46
|
export { generateRlsPolicy, authSchemaHelpers } from "./rls-policy";
|
|
36
47
|
export { InMemoryPgPool } from "./in-memory-pool";
|
|
37
|
-
export {
|
|
48
|
+
export { DB_RLS_METRIC_VOCABULARY, _resetMetricsWarnLedgerForTests, } from "./metrics";
|
|
49
|
+
export { DbRlsConfigError, DbRlsError, DbRlsNotInitialized, MissingTenantContext, OwnerContextOutsideMigrate, PoolUnavailable, TransactionRolledBack, } from "./types";
|
|
38
50
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,wCAAwC;AACxC,EAAE;AACF,oEAAoE;AACpE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,wCAAwC;AACxC,EAAE;AACF,oEAAoE;AACpE,oEAAoE;AACpE,wDAAwD;AACxD,6CAA6C;AAC7C,EAAE;AACF,yCAAyC;AACzC,sEAAsE;AACtE,wCAAwC;AACxC,sDAAsD;AACtD,+DAA+D;AAC/D,4EAA4E;AAC5E,gDAAgD;AAChD,mEAAmE;AACnE,mDAAmD;AACnD,wEAAwE;AACxE,2EAA2E;AAC3E,+EAA+E;AAC/E,wEAAwE;AACxE,sEAAsE;AACtE,6EAA6E;AAC7E,6EAA6E;AAC7E,sCAAsC;AACtC,uEAAuE;AACvE,0EAA0E;AAC1E,8EAA8E;AAC9E,kFAAkF;AAClF,yEAAyE;AACzE,mEAAmE;AACnE,0EAA0E;AAC1E,qCAAqC;AAErC,MAAM,CAAC,MAAM,QAAQ,GAAG,QAAQ,CAAC;AACjC,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC;AAE/B,OAAO,EACL,SAAS,EACT,YAAY,EACZ,cAAc,EACd,WAAW,EACX,cAAc,GACf,MAAM,QAAQ,CAAC;AAGhB,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,mBAAmB,EACnB,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,aAAa,CAAC;AAMrB,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGjD,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,gBAAgB,EAChB,eAAe,EACf,YAAY,GACb,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC;AAI1B,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAEvD,OAAO,EACL,eAAe,EACf,sBAAsB,EACtB,yBAAyB,EACzB,iBAAiB,GAClB,MAAM,eAAe,CAAC;AAGvB,OAAO,EAAE,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAGhE,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,eAAe,CAAC;AAGvB,qEAAqE;AACrE,oEAAoE;AACpE,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEpE,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAGlD,OAAO,EACL,wBAAwB,EACxB,+BAA+B,GAChC,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,gBAAgB,EAChB,UAAU,EACV,mBAAmB,EACnB,oBAAoB,EACpB,0BAA0B,EAC1B,eAAe,EACf,qBAAqB,GACtB,MAAM,SAAS,CAAC"}
|
package/dist/init.d.ts
CHANGED
|
@@ -1,13 +1,31 @@
|
|
|
1
|
-
import { type PgPool, type PoolKind } from "./types";
|
|
1
|
+
import { type DbRlsMode, type PgPool, type PoolKind } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Process-local config. `services` and `authenticated` are optional ONLY
|
|
4
|
+
* in 2-pool mode; in 3-pool mode (the default) the type guard runs at
|
|
5
|
+
* init time and throws `DbRlsConfigError` if either is missing.
|
|
6
|
+
*
|
|
7
|
+
* v0.4.0 (D185): `platformOps` is REQUIRED when `mode:
|
|
8
|
+
* "3-pool-with-platform-ops"` and FORBIDDEN otherwise.
|
|
9
|
+
*/
|
|
2
10
|
export interface InitDbRlsConfig {
|
|
3
11
|
owner: PgPool;
|
|
4
|
-
services
|
|
5
|
-
authenticated
|
|
12
|
+
services?: PgPool;
|
|
13
|
+
authenticated?: PgPool;
|
|
14
|
+
/** D185 platform-ops pool. Required in 4-pool mode; forbidden in
|
|
15
|
+
* 3-pool / 2-pool modes. RLS-bound (NOT BYPASSRLS); policies use
|
|
16
|
+
* the grants-lookup predicate against `platform_ops_grants_replica`. */
|
|
17
|
+
platformOps?: PgPool;
|
|
18
|
+
/** Defaults to `"3-pool"`. Set `"2-pool"` for nodii-auth-shape services
|
|
19
|
+
* per 08-rls § 7. Set `"3-pool-with-platform-ops"` for services with a
|
|
20
|
+
* platform-ops surface per D185 (nodii-tenant-service, etc.). */
|
|
21
|
+
mode?: DbRlsMode;
|
|
6
22
|
}
|
|
7
23
|
export interface ResolvedDbRls {
|
|
8
24
|
owner: PgPool;
|
|
9
|
-
services: PgPool;
|
|
10
|
-
authenticated: PgPool;
|
|
25
|
+
services: PgPool | null;
|
|
26
|
+
authenticated: PgPool | null;
|
|
27
|
+
platform_ops: PgPool | null;
|
|
28
|
+
mode: DbRlsMode;
|
|
11
29
|
}
|
|
12
30
|
export declare function initDbRls(config: InitDbRlsConfig): void;
|
|
13
31
|
export declare function requireDbRls(): ResolvedDbRls;
|
package/dist/init.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAUA,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,MAAM,EAEX,KAAK,QAAQ,EACd,MAAM,SAAS,CAAC;AAEjB;;;;;;;GAOG;AACH,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;6EAEyE;IACzE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;sEAEkE;IAClE,IAAI,CAAC,EAAE,SAAS,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,IAAI,EAAE,SAAS,CAAC;CACjB;AAID,wBAAgB,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CAwDvD;AAED,wBAAgB,YAAY,IAAI,aAAa,CAK5C;AAED,wBAAgB,cAAc,IAAI,aAAa,GAAG,IAAI,CAErD;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAOlD;AAED,+DAA+D;AAC/D,wBAAgB,cAAc,IAAI,IAAI,CAErC"}
|
package/dist/init.js
CHANGED
|
@@ -2,18 +2,52 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Spec § 5.2. Caller supplies one PgPool per role. Subsequent calls warn +
|
|
4
4
|
// no-op (first-init wins). Public APIs throw `DbRlsNotInitialized` before init.
|
|
5
|
-
|
|
5
|
+
//
|
|
6
|
+
// v0.2.x adds the 2-pool exception per `08-rls-doctrine § 7` — nodii-auth
|
|
7
|
+
// boots without `services`/`authenticated` until its admin portal lands.
|
|
8
|
+
// Mode is explicit (`"3-pool"` | `"2-pool"`) so a missing pool is never
|
|
9
|
+
// silent.
|
|
10
|
+
import { DbRlsConfigError, DbRlsNotInitialized, PoolUnavailable, } from "./types";
|
|
6
11
|
let resolved = null;
|
|
7
12
|
export function initDbRls(config) {
|
|
8
13
|
if (resolved) {
|
|
9
|
-
// eslint-disable-next-line no-console
|
|
10
14
|
console.warn("[@nodii/db-rls] initDbRls() called more than once; ignoring subsequent call.");
|
|
11
15
|
return;
|
|
12
16
|
}
|
|
17
|
+
const mode = config.mode ?? "3-pool";
|
|
18
|
+
if (mode === "3-pool") {
|
|
19
|
+
if (!config.services || !config.authenticated) {
|
|
20
|
+
throw new DbRlsConfigError("initDbRls: 3-pool mode requires owner + services + authenticated pools. Either supply both, or set mode: '2-pool' (nodii-auth shape per 08-rls § 7).");
|
|
21
|
+
}
|
|
22
|
+
if (config.platformOps) {
|
|
23
|
+
throw new DbRlsConfigError("initDbRls: 3-pool mode must not supply a 'platformOps' pool — use mode: '3-pool-with-platform-ops' per hub decision D185 if you have a platform-ops surface.");
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
// 2-pool: services optional, authenticated forbidden (the whole point
|
|
27
|
+
// of the exception is that nodii-auth has no user-context surface yet).
|
|
28
|
+
if (mode === "2-pool") {
|
|
29
|
+
if (config.authenticated) {
|
|
30
|
+
throw new DbRlsConfigError("initDbRls: 2-pool mode must not supply an 'authenticated' pool — the 2-pool exception per 08-rls § 7 covers services without user-context. Use mode: '3-pool' if you have an authenticated pool.");
|
|
31
|
+
}
|
|
32
|
+
if (config.platformOps) {
|
|
33
|
+
throw new DbRlsConfigError("initDbRls: 2-pool mode must not supply a 'platformOps' pool — the 2-pool exception per 08-rls § 7 has no platform-ops surface. Use mode: '3-pool-with-platform-ops' per D185.");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// 4-pool (D185): owner + services + authenticated + platformOps all required.
|
|
37
|
+
if (mode === "3-pool-with-platform-ops") {
|
|
38
|
+
if (!config.services || !config.authenticated) {
|
|
39
|
+
throw new DbRlsConfigError("initDbRls: '3-pool-with-platform-ops' mode requires owner + services + authenticated + platformOps pools (D185). All four are mandatory.");
|
|
40
|
+
}
|
|
41
|
+
if (!config.platformOps) {
|
|
42
|
+
throw new DbRlsConfigError("initDbRls: '3-pool-with-platform-ops' mode requires a 'platformOps' pool wired against the nodii_platform_ops Postgres role (D185 § 1). Did you mean mode: '3-pool'?");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
13
45
|
resolved = {
|
|
14
46
|
owner: config.owner,
|
|
15
|
-
services: config.services,
|
|
16
|
-
authenticated: config.authenticated,
|
|
47
|
+
services: config.services ?? null,
|
|
48
|
+
authenticated: config.authenticated ?? null,
|
|
49
|
+
platform_ops: config.platformOps ?? null,
|
|
50
|
+
mode,
|
|
17
51
|
};
|
|
18
52
|
}
|
|
19
53
|
export function requireDbRls() {
|
package/dist/init.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.js","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,EAAE;AACF,2EAA2E;AAC3E,gFAAgF;
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,EAAE;AACF,2EAA2E;AAC3E,gFAAgF;AAChF,EAAE;AACF,0EAA0E;AAC1E,yEAAyE;AACzE,wEAAwE;AACxE,UAAU;AAEV,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EAGnB,eAAe,GAEhB,MAAM,SAAS,CAAC;AAgCjB,IAAI,QAAQ,GAAyB,IAAI,CAAC;AAE1C,MAAM,UAAU,SAAS,CAAC,MAAuB;IAC/C,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,CAAC,IAAI,CACV,8EAA8E,CAC/E,CAAC;QACF,OAAO;IACT,CAAC;IAED,MAAM,IAAI,GAAc,MAAM,CAAC,IAAI,IAAI,QAAQ,CAAC;IAChD,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtB,IAAI,CAAC,MAAM,CAAC,QAAQ,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC;YAC9C,MAAM,IAAI,gBAAgB,CACxB,sJAAsJ,CACvJ,CAAC;QACJ,CAAC;QACD,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;YACvB,MAAM,IAAI,gBAAgB,CACxB,8JAA8J,CAC/J,CAAC;QACJ,CAAC;IACH,CAAC;IACD,sEAAsE;IACtE,wEAAwE;IACxE,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtB,IAAI,MAAM,CAAC,aAAa,EAAE,CAAC;YACzB,MAAM,IAAI,gBAAgB,CACxB,kMAAkM,CACnM,CAAC;QACJ,CAAC;QACD,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;YACvB,MAAM,IAAI,gBAAgB,CACxB,+KAA+K,CAChL,CAAC;QACJ,CAAC;IACH,CAAC;IACD,8EAA8E;IAC9E,IAAI,IAAI,KAAK,0BAA0B,EAAE,CAAC;QACxC,IAAI,CAAC,MAAM,CAAC,QAAQ,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC;YAC9C,MAAM,IAAI,gBAAgB,CACxB,0IAA0I,CAC3I,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;YACxB,MAAM,IAAI,gBAAgB,CACxB,sKAAsK,CACvK,CAAC;QACJ,CAAC;IACH,CAAC;IAED,QAAQ,GAAG;QACT,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,IAAI;QACjC,aAAa,EAAE,MAAM,CAAC,aAAa,IAAI,IAAI;QAC3C,YAAY,EAAE,MAAM,CAAC,WAAW,IAAI,IAAI;QACxC,IAAI;KACL,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,mBAAmB,EAAE,CAAC;IAClC,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAc;IACxC,MAAM,GAAG,GAAG,YAAY,EAAE,CAAC;IAC3B,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,eAAe,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,cAAc;IAC5B,QAAQ,GAAG,IAAI,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type MetricVocabularyRegistry } from "@nodii/telemetry";
|
|
2
|
+
import type { PoolKind } from "./types";
|
|
3
|
+
export declare const DB_RLS_METRIC_VOCABULARY: MetricVocabularyRegistry;
|
|
4
|
+
export declare function recordAcquisition(pool: PoolKind, outcome: "success" | "failure"): void;
|
|
5
|
+
export declare function recordAcquisitionDuration(pool: PoolKind, durationMs: number): void;
|
|
6
|
+
export declare function recordPoolSize(pool: PoolKind, state: "active" | "idle" | "waiting", size: number): void;
|
|
7
|
+
export declare function recordTxDuration(pool: PoolKind, outcome: "committed" | "rolled_back" | "error", durationMs: number): void;
|
|
8
|
+
export declare function recordCrossPoolNesting(outer: PoolKind, inner: PoolKind, reason: string): void;
|
|
9
|
+
export declare function recordCrossPoolViolation(outer: PoolKind, attemptedInner: PoolKind): void;
|
|
10
|
+
export declare function recordOwnerContextInvocation(outcome: "allowed" | "refused"): void;
|
|
11
|
+
export declare function recordGucSetFailure(guc: string): void;
|
|
12
|
+
/**
|
|
13
|
+
* Wraps `fn` in a `db.rls.tx` span scoped to `pool`. The span stamps
|
|
14
|
+
* the final outcome attribute (`committed` / `rolled_back` / `error`)
|
|
15
|
+
* via the `setOutcome` callback the caller passes through. On thrown
|
|
16
|
+
* exceptions the tracer.span lifecycle marks status=ERROR + records
|
|
17
|
+
* the exception — no extra work needed here.
|
|
18
|
+
*/
|
|
19
|
+
export declare function withTxSpan<T>(pool: PoolKind, fn: (setOutcome: (o: "committed" | "rolled_back" | "error") => void) => Promise<T>): Promise<T>;
|
|
20
|
+
/**
|
|
21
|
+
* Emits a one-shot `db.rls.pool.acquire` span. Caller invokes this only
|
|
22
|
+
* when acquisition time exceeds the slow threshold (default 50ms per
|
|
23
|
+
* § 5.10) — keeps span volume bounded.
|
|
24
|
+
*/
|
|
25
|
+
export declare function emitSlowAcquireSpan(pool: PoolKind, durationMs: number): void;
|
|
26
|
+
/** Test-only: reset the one-shot warn ledger so each test runs clean. */
|
|
27
|
+
export declare function _resetMetricsWarnLedgerForTests(): void;
|
|
28
|
+
//# sourceMappingURL=metrics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../src/metrics.ts"],"names":[],"mappings":"AAmBA,OAAO,EAEL,KAAK,wBAAwB,EAI9B,MAAM,kBAAkB,CAAC;AAE1B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAWxC,eAAO,MAAM,wBAAwB,EAAE,wBAsDtC,CAAC;AAuFF,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,QAAQ,EACd,OAAO,EAAE,SAAS,GAAG,SAAS,GAC7B,IAAI,CAKN;AAED,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,QAAQ,EACd,UAAU,EAAE,MAAM,GACjB,IAAI,CAMN;AAED,wBAAgB,cAAc,CAC5B,IAAI,EAAE,QAAQ,EACd,KAAK,EAAE,QAAQ,GAAG,MAAM,GAAG,SAAS,EACpC,IAAI,EAAE,MAAM,GACX,IAAI,CAMN;AAED,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,QAAQ,EACd,OAAO,EAAE,WAAW,GAAG,aAAa,GAAG,OAAO,EAC9C,UAAU,EAAE,MAAM,GACjB,IAAI,CAUN;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,QAAQ,EACf,KAAK,EAAE,QAAQ,EACf,MAAM,EAAE,MAAM,GACb,IAAI,CAKN;AAED,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,QAAQ,EACf,cAAc,EAAE,QAAQ,GACvB,IAAI,CAKN;AAED,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,SAAS,GAAG,SAAS,GAC7B,IAAI,CAKN;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAKrD;AAQD;;;;;;GAMG;AACH,wBAAsB,UAAU,CAAC,CAAC,EAChC,IAAI,EAAE,QAAQ,EACd,EAAE,EAAE,CACF,UAAU,EAAE,CAAC,CAAC,EAAE,WAAW,GAAG,aAAa,GAAG,OAAO,KAAK,IAAI,KAC3D,OAAO,CAAC,CAAC,CAAC,GACd,OAAO,CAAC,CAAC,CAAC,CA8BZ;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAgB5E;AAKD,yEAAyE;AACzE,wBAAgB,+BAA+B,IAAI,IAAI,CAGtD"}
|
package/dist/metrics.js
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
// 9 locked db-rls metrics + 2 spans per `db-rls.md § 5.10` + doctrine
|
|
2
|
+
// `08-rls-doctrine.md § 12.4`. Wired through `@nodii/telemetry`'s
|
|
3
|
+
// vocabulary-bound `metrics.counter` / `metrics.histogram` API.
|
|
4
|
+
//
|
|
5
|
+
// Behavioral discipline:
|
|
6
|
+
// - Each metric is registered lazily on first emit. If the consumer
|
|
7
|
+
// hasn't merged `DB_RLS_METRIC_VOCABULARY` into their
|
|
8
|
+
// `initTelemetry({ vocabularyRegistry })` call, the registration
|
|
9
|
+
// throws `MetricVocabularyError`. We catch + `console.warn` once per
|
|
10
|
+
// metric so adopters see the gap; subsequent emissions are silently
|
|
11
|
+
// no-op for that name. Mirrors the pattern in
|
|
12
|
+
// `ts/approval/src/telemetry-shim.ts`.
|
|
13
|
+
// - Consumers wire `DB_RLS_METRIC_VOCABULARY` into their telemetry init
|
|
14
|
+
// so the registrations succeed; from then on the metrics emit
|
|
15
|
+
// normally.
|
|
16
|
+
// - Spans are emitted via `@nodii/telemetry`'s `tracer.startSpan`. Same
|
|
17
|
+
// try/swallow pattern — `TelemetryNotInitialized` is caught + the
|
|
18
|
+
// fn runs without a span.
|
|
19
|
+
import { MetricVocabularyError, metrics, tracer, TelemetryNotInitialized, } from "@nodii/telemetry";
|
|
20
|
+
/**
|
|
21
|
+
* Vocabulary registration. Adopters merge this into their `initTelemetry({
|
|
22
|
+
* vocabularyRegistry })` call so the metrics emitted by `@nodii/db-rls`
|
|
23
|
+
* are recognized.
|
|
24
|
+
*/
|
|
25
|
+
// All 9 metrics map to D18 family `pool` + the doctrine § 12.4 decision
|
|
26
|
+
// `D-rls-metrics-v1` (recorded in the locked db-rls.md § 5.10 table).
|
|
27
|
+
const DOCTRINE_DECISION = "D-rls-metrics-v1";
|
|
28
|
+
export const DB_RLS_METRIC_VOCABULARY = {
|
|
29
|
+
"db_rls.pool.acquisitions_total": {
|
|
30
|
+
family: "pool",
|
|
31
|
+
description: "Pool connection acquisitions per pool + outcome (success|failure).",
|
|
32
|
+
decision: DOCTRINE_DECISION,
|
|
33
|
+
},
|
|
34
|
+
"db_rls.pool.acquisition_duration_seconds": {
|
|
35
|
+
family: "pool",
|
|
36
|
+
description: "Duration (s) from pool.connect() request to client ready.",
|
|
37
|
+
decision: DOCTRINE_DECISION,
|
|
38
|
+
},
|
|
39
|
+
"db_rls.pool.size": {
|
|
40
|
+
family: "pool",
|
|
41
|
+
description: "Pool size sampled at tx finish; state ∈ {active, idle, waiting}.",
|
|
42
|
+
decision: DOCTRINE_DECISION,
|
|
43
|
+
},
|
|
44
|
+
"db_rls.tx.duration_seconds": {
|
|
45
|
+
family: "pool",
|
|
46
|
+
description: "Per-transaction duration (s) from BEGIN to COMMIT/ROLLBACK, per pool + outcome.",
|
|
47
|
+
decision: DOCTRINE_DECISION,
|
|
48
|
+
},
|
|
49
|
+
"db_rls.tx.outcomes_total": {
|
|
50
|
+
family: "pool",
|
|
51
|
+
description: "Transaction outcomes per pool. outcome ∈ {committed, rolled_back, error}.",
|
|
52
|
+
decision: DOCTRINE_DECISION,
|
|
53
|
+
},
|
|
54
|
+
"db_rls.cross_pool_nesting_total": {
|
|
55
|
+
family: "pool",
|
|
56
|
+
description: "Sanctioned cross-pool nesting events ({outer}→{inner}, with reason).",
|
|
57
|
+
decision: DOCTRINE_DECISION,
|
|
58
|
+
},
|
|
59
|
+
"db_rls.cross_pool_violations_total": {
|
|
60
|
+
family: "pool",
|
|
61
|
+
description: "Refused cross-pool nesting attempts ({outer}→{attempted_inner}).",
|
|
62
|
+
decision: DOCTRINE_DECISION,
|
|
63
|
+
},
|
|
64
|
+
"db_rls.owner_context_invocations_total": {
|
|
65
|
+
family: "pool",
|
|
66
|
+
description: "withOwnerContext entry events. Should be 0 in non-migration prod runtime.",
|
|
67
|
+
decision: DOCTRINE_DECISION,
|
|
68
|
+
},
|
|
69
|
+
"db_rls.guc_set_failures_total": {
|
|
70
|
+
family: "pool",
|
|
71
|
+
description: "GUC set_config failures by GUC name — indicates auth.* helper drift.",
|
|
72
|
+
decision: DOCTRINE_DECISION,
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* One-shot warn ledger for metric names whose registration failed (e.g.
|
|
77
|
+
* because the consumer didn't merge `DB_RLS_METRIC_VOCABULARY`). Each
|
|
78
|
+
* name warn-logs once; subsequent emissions of that name are silent.
|
|
79
|
+
*/
|
|
80
|
+
const WARNED = new Set();
|
|
81
|
+
let TELEMETRY_WARNED = false;
|
|
82
|
+
function warnOnce(metric, err) {
|
|
83
|
+
if (WARNED.has(metric))
|
|
84
|
+
return;
|
|
85
|
+
WARNED.add(metric);
|
|
86
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
87
|
+
console.warn(`[@nodii/db-rls] metric '${metric}' swallowed: ${msg}. Merge DB_RLS_METRIC_VOCABULARY into your initTelemetry({ vocabularyRegistry }) call so db-rls metrics emit. Further failures for '${metric}' will be silent.`);
|
|
88
|
+
}
|
|
89
|
+
function warnTelemetryOnce(err) {
|
|
90
|
+
if (TELEMETRY_WARNED)
|
|
91
|
+
return;
|
|
92
|
+
TELEMETRY_WARNED = true;
|
|
93
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
94
|
+
console.warn(`[@nodii/db-rls] telemetry not initialized: ${msg}. Call initTelemetry({ vocabularyRegistry: { ...DB_RLS_METRIC_VOCABULARY, ... } }) at process start. Further telemetry failures will be silent.`);
|
|
95
|
+
}
|
|
96
|
+
function inc(args) {
|
|
97
|
+
try {
|
|
98
|
+
const handle = metrics.counter(args.name, {
|
|
99
|
+
family: "pool",
|
|
100
|
+
description: DB_RLS_METRIC_VOCABULARY[args.name]?.description ?? "",
|
|
101
|
+
labelKeys: args.labels ? Object.keys(args.labels) : [],
|
|
102
|
+
});
|
|
103
|
+
handle.inc(args.by ?? 1, args.labels ?? {});
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
if (err instanceof MetricVocabularyError) {
|
|
107
|
+
warnOnce(args.name, err);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (err instanceof TelemetryNotInitialized) {
|
|
111
|
+
warnTelemetryOnce(err);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
// Anything else: also one-shot warn, never throw out of the metric path.
|
|
115
|
+
warnOnce(args.name, err);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function observe(args) {
|
|
119
|
+
try {
|
|
120
|
+
const handle = metrics.histogram(args.name, {
|
|
121
|
+
family: "pool",
|
|
122
|
+
description: DB_RLS_METRIC_VOCABULARY[args.name]?.description ?? "",
|
|
123
|
+
labelKeys: args.labels ? Object.keys(args.labels) : [],
|
|
124
|
+
});
|
|
125
|
+
handle.record(args.value, args.labels ?? {});
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
if (err instanceof MetricVocabularyError) {
|
|
129
|
+
warnOnce(args.name, err);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (err instanceof TelemetryNotInitialized) {
|
|
133
|
+
warnTelemetryOnce(err);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
warnOnce(args.name, err);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
// Public emission helpers — called by PgClientPool / PgPostgresJsPool /
|
|
141
|
+
// context.ts. Each helper is a thin shim so the call sites stay readable.
|
|
142
|
+
export function recordAcquisition(pool, outcome) {
|
|
143
|
+
inc({
|
|
144
|
+
name: "db_rls.pool.acquisitions_total",
|
|
145
|
+
labels: { pool, outcome },
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
export function recordAcquisitionDuration(pool, durationMs) {
|
|
149
|
+
observe({
|
|
150
|
+
name: "db_rls.pool.acquisition_duration_seconds",
|
|
151
|
+
value: durationMs / 1000,
|
|
152
|
+
labels: { pool },
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
export function recordPoolSize(pool, state, size) {
|
|
156
|
+
observe({
|
|
157
|
+
name: "db_rls.pool.size",
|
|
158
|
+
value: size,
|
|
159
|
+
labels: { pool, state },
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
export function recordTxDuration(pool, outcome, durationMs) {
|
|
163
|
+
observe({
|
|
164
|
+
name: "db_rls.tx.duration_seconds",
|
|
165
|
+
value: durationMs / 1000,
|
|
166
|
+
labels: { pool, outcome },
|
|
167
|
+
});
|
|
168
|
+
inc({
|
|
169
|
+
name: "db_rls.tx.outcomes_total",
|
|
170
|
+
labels: { pool, outcome },
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
export function recordCrossPoolNesting(outer, inner, reason) {
|
|
174
|
+
inc({
|
|
175
|
+
name: "db_rls.cross_pool_nesting_total",
|
|
176
|
+
labels: { outer, inner, reason },
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
export function recordCrossPoolViolation(outer, attemptedInner) {
|
|
180
|
+
inc({
|
|
181
|
+
name: "db_rls.cross_pool_violations_total",
|
|
182
|
+
labels: { outer, attempted_inner: attemptedInner },
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
export function recordOwnerContextInvocation(outcome) {
|
|
186
|
+
inc({
|
|
187
|
+
name: "db_rls.owner_context_invocations_total",
|
|
188
|
+
labels: { outcome },
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
export function recordGucSetFailure(guc) {
|
|
192
|
+
inc({
|
|
193
|
+
name: "db_rls.guc_set_failures_total",
|
|
194
|
+
labels: { guc },
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
// Spans — `db.rls.tx{pool}` per § 5.10 wraps the tx fn so the span end
|
|
199
|
+
// runs deterministically on success + throw. `db.rls.pool.acquire{pool}`
|
|
200
|
+
// is emitted as a one-shot span only when acquisition exceeds the slow
|
|
201
|
+
// threshold (default 50ms) per the doctrine.
|
|
202
|
+
/**
|
|
203
|
+
* Wraps `fn` in a `db.rls.tx` span scoped to `pool`. The span stamps
|
|
204
|
+
* the final outcome attribute (`committed` / `rolled_back` / `error`)
|
|
205
|
+
* via the `setOutcome` callback the caller passes through. On thrown
|
|
206
|
+
* exceptions the tracer.span lifecycle marks status=ERROR + records
|
|
207
|
+
* the exception — no extra work needed here.
|
|
208
|
+
*/
|
|
209
|
+
export async function withTxSpan(pool, fn) {
|
|
210
|
+
try {
|
|
211
|
+
return await tracer.span("db.rls.tx", async (span) => {
|
|
212
|
+
let outcome = "error";
|
|
213
|
+
try {
|
|
214
|
+
const out = await fn((o) => {
|
|
215
|
+
outcome = o;
|
|
216
|
+
});
|
|
217
|
+
span.setAttribute("outcome", outcome);
|
|
218
|
+
return out;
|
|
219
|
+
}
|
|
220
|
+
catch (err) {
|
|
221
|
+
span.setAttribute("outcome", "error");
|
|
222
|
+
throw err;
|
|
223
|
+
}
|
|
224
|
+
}, { attributes: { pool } });
|
|
225
|
+
}
|
|
226
|
+
catch (err) {
|
|
227
|
+
if (err instanceof TelemetryNotInitialized) {
|
|
228
|
+
warnTelemetryOnce(err);
|
|
229
|
+
// Telemetry not initialized — degrade to a plain pass-through.
|
|
230
|
+
let outcome = "error";
|
|
231
|
+
return await fn((o) => {
|
|
232
|
+
outcome = o;
|
|
233
|
+
}).finally(() => void outcome);
|
|
234
|
+
}
|
|
235
|
+
throw err;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Emits a one-shot `db.rls.pool.acquire` span. Caller invokes this only
|
|
240
|
+
* when acquisition time exceeds the slow threshold (default 50ms per
|
|
241
|
+
* § 5.10) — keeps span volume bounded.
|
|
242
|
+
*/
|
|
243
|
+
export function emitSlowAcquireSpan(pool, durationMs) {
|
|
244
|
+
// Fire-and-forget single-tick span — no fn body to wrap.
|
|
245
|
+
void tracer
|
|
246
|
+
.span("db.rls.pool.acquire", async (_span) => {
|
|
247
|
+
/* attributes set via opts; no inner work. */
|
|
248
|
+
}, { attributes: { pool, "duration.ms": durationMs } })
|
|
249
|
+
.catch((err) => {
|
|
250
|
+
if (err instanceof TelemetryNotInitialized) {
|
|
251
|
+
warnTelemetryOnce(err);
|
|
252
|
+
}
|
|
253
|
+
/* otherwise swallow */
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
// ---------------------------------------------------------------------------
|
|
257
|
+
// Test hooks.
|
|
258
|
+
/** Test-only: reset the one-shot warn ledger so each test runs clean. */
|
|
259
|
+
export function _resetMetricsWarnLedgerForTests() {
|
|
260
|
+
WARNED.clear();
|
|
261
|
+
TELEMETRY_WARNED = false;
|
|
262
|
+
}
|
|
263
|
+
//# sourceMappingURL=metrics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics.js","sourceRoot":"","sources":["../src/metrics.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface OwnerContextOpts {
|
|
2
|
+
/** Bypass the `NODII_ENV=migrate` guard. Requires `reason` to be a
|
|
3
|
+
* non-empty string so the bypass is at least intentional. v0.2.x:
|
|
4
|
+
* the reason is captured for log lines + future audit emission. */
|
|
5
|
+
allowOutsideMigrate?: boolean;
|
|
6
|
+
/** Operator-attested reason, required iff `allowOutsideMigrate` is true. */
|
|
7
|
+
reason?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Asserts the caller can legitimately enter `withOwnerContext`. Throws
|
|
11
|
+
* `OwnerContextOutsideMigrate` if the guard is not satisfied.
|
|
12
|
+
*/
|
|
13
|
+
export declare function assertMigrateAllowed(opts?: OwnerContextOpts): void;
|
|
14
|
+
//# sourceMappingURL=migrate-guard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrate-guard.d.ts","sourceRoot":"","sources":["../src/migrate-guard.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,gBAAgB;IAC/B;;wEAEoE;IACpE,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,4EAA4E;IAC5E,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,GAAE,gBAAqB,GAAG,IAAI,CAWtE"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// migrate-guard — per `08-rls-doctrine § 10.2`.
|
|
2
|
+
//
|
|
3
|
+
// `withOwnerContext` / `withOwnerContextDb` connect via `nodii_owner`
|
|
4
|
+
// which is BYPASSRLS. Invoking it outside a migration is a tenant-
|
|
5
|
+
// isolation escape: an owner-pool query against the live request path
|
|
6
|
+
// returns ALL tenants' rows. The guard refuses by default unless:
|
|
7
|
+
//
|
|
8
|
+
// 1. `NODII_ENV=migrate` (migration scripts opt in via env), OR
|
|
9
|
+
// 2. The caller explicitly passes `{ allowOutsideMigrate: true,
|
|
10
|
+
// reason: "..." }` with an operator-attested reason.
|
|
11
|
+
//
|
|
12
|
+
// Both paths are first-class — option 2 exists for legitimate ops
|
|
13
|
+
// tooling (DSAR fulfillment, support-ticket data extraction) where the
|
|
14
|
+
// caller has attested authorization. v0.2.x ships the guard; the
|
|
15
|
+
// audit-row-on-bypass extension lands in a follow-up slot.
|
|
16
|
+
import { OwnerContextOutsideMigrate } from "./types";
|
|
17
|
+
/**
|
|
18
|
+
* Asserts the caller can legitimately enter `withOwnerContext`. Throws
|
|
19
|
+
* `OwnerContextOutsideMigrate` if the guard is not satisfied.
|
|
20
|
+
*/
|
|
21
|
+
export function assertMigrateAllowed(opts = {}) {
|
|
22
|
+
if (opts.allowOutsideMigrate) {
|
|
23
|
+
if (!opts.reason || opts.reason.trim() === "") {
|
|
24
|
+
throw new OwnerContextOutsideMigrate(process.env.NODII_ENV ?? "(unset)");
|
|
25
|
+
}
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const env = process.env.NODII_ENV ?? "";
|
|
29
|
+
if (env !== "migrate") {
|
|
30
|
+
throw new OwnerContextOutsideMigrate(env === "" ? "(unset)" : env);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=migrate-guard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrate-guard.js","sourceRoot":"","sources":["../src/migrate-guard.ts"],"names":[],"mappings":"AAAA,gDAAgD;AAChD,EAAE;AACF,sEAAsE;AACtE,mEAAmE;AACnE,sEAAsE;AACtE,kEAAkE;AAClE,EAAE;AACF,kEAAkE;AAClE,kEAAkE;AAClE,0DAA0D;AAC1D,EAAE;AACF,kEAAkE;AAClE,uEAAuE;AACvE,iEAAiE;AACjE,2DAA2D;AAE3D,OAAO,EAAE,0BAA0B,EAAE,MAAM,SAAS,CAAC;AAWrD;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAyB,EAAE;IAC9D,IAAI,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAC7B,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YAC9C,MAAM,IAAI,0BAA0B,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,SAAS,CAAC,CAAC;QAC3E,CAAC;QACD,OAAO;IACT,CAAC;IACD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC;IACxC,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QACtB,MAAM,IAAI,0BAA0B,CAAC,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACrE,CAAC;AACH,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pg-pool-adapter.d.ts","sourceRoot":"","sources":["../src/pg-pool-adapter.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,IAAI,EAAc,MAAM,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"pg-pool-adapter.d.ts","sourceRoot":"","sources":["../src/pg-pool-adapter.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,IAAI,EAAc,MAAM,IAAI,CAAC;AAU3C,OAAO,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEtD,MAAM,WAAW,iBAAiB;IAChC,qEAAqE;IACrE,IAAI,EAAE,IAAI,CAAC;IACX,sDAAsD;IACtD,IAAI,EAAE,QAAQ,CAAC;IACf;uDACmD;IACnD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;qDACiD;IACjD,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACvE;qDACiD;IACjD,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE;QAClB,IAAI,EAAE,QAAQ,CAAC;QACf,OAAO,EAAE,WAAW,GAAG,aAAa,GAAG,OAAO,CAAC;QAC/C,UAAU,EAAE,MAAM,CAAC;KACpB,KAAK,IAAI,CAAC;CACZ;AA2BD;;;;GAIG;AACH,qBAAa,YAAa,YAAW,MAAM;IACzC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAO;IAC5B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAqC;IACpE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAkC;gBAElD,IAAI,EAAE,iBAAiB;IAQnC;;;sEAGkE;IAClE,IAAI,OAAO,IAAI,IAAI,CAElB;IAEK,eAAe,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;CA2FnE"}
|