feat(quick-260914-eym): forSystem(), is_system_context(), Systemleseregel auf fuenf Tabellen, DKV-Planer je Mandant — ein Pfad (WINDOWS #21)

- Helfer forSystem(prisma) in prisma-tenant.extension.ts (Array-Form,
  setzt app.system_context='true' und die beiden anderen Variablen
  ausdruecklich leer); forTenant()/withTenantTransaction() setzen
  app.system_context='' als Literal (4 neue Spec-Tests)
- Migration 20260914120000_rls_system_context_read: is_system_context()
  (COALESCE, STABLE) und system_read_policy FOR SELECT auf DkvModuleConfig,
  LdapConfig, LdapFieldMapping, TenderMatch, TenderSavedSearch — lokal
  angewendet (36 Migrationen, pg_proc 1, 5 system_read_policy, 34 Regeln)
- migration-sql.spec.ts: describe-Block fuer die neue Migration (6 Tests)
- rls-scratch-check.mjs: Funktion aus der Migration geschnitten,
  forSystemQuery/buildInlineSystemClient, Reset in forTenantQuery/
  buildInlineExtendedClient, runSystemContextChecks (4 Funktionsfaelle +
  9 Kennungen DkvModuleConfig) -> Alle 216 Pruefungen bestanden
- rls-access-inventory.spec.ts: fuenfte Erkennungsform const X = forSystem(,
  Stand system-gebunden mit Vorrangregel, FORSYSTEM_ALLOWED_CALL_SITES
  (exakte Zahl je Datei, 3 Tests), Proben C/D/E
- DKV: loadActiveConfigsForScheduler() ueber forSystem (findMany isActive,
  CONFIG_SAFE_SELECT, orderBy tenantId); DkvSchedulerService mit Auftrag je
  Mandant dkv-inbox-poll:<tenantId>, activeTenantId ersatzlos entfernt,
  setInterval/stopJob je Mandant, registeredTenantIds(); Controller
  stopJob(tenantId); neue dkv-scheduler.service.spec.ts (7 Tests),
  dkv.service.spec.ts Tests 6/7 umgestellt
- Klassifikation: dkv.service.ts/dkvModuleConfig system-gebunden, Header
  mit fuenfter Erkennungsform und viertem Stand-Wert
- Baseline: 63 Dateien / 1051 Tests, tsc 0, Werkzeug 216

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
This commit is contained in:
2026-09-14 11:32:54 +02:00
parent 02016e19eb
commit 3d645674f0
12 changed files with 1272 additions and 139 deletions
+61
View File
@@ -255,6 +255,67 @@ describe('rls_user_dimension_personal_tables migration.sql (Etappe 3b, 260911-nk
});
});
describe('rls_system_context_read migration.sql (Etappe 3c, 260914-eym)', () => {
const sql = readMigrationSql('_rls_system_context_read');
const SYSTEM_READ_TABLES = ['DkvModuleConfig', 'LdapConfig', 'LdapFieldMapping', 'TenderMatch', 'TenderSavedSearch'];
const NOT_OPENED_TABLES = ['SmtpConfig', 'Tenant', 'Tender'];
function nonCommentLines(source: string): string {
return source
.split('\n')
.filter((line) => !line.trim().startsWith('--'))
.join('\n');
}
function policyStatements(source: string): string[] {
return (nonCommentLines(source).match(/CREATE POLICY [\w]+ ON "[A-Za-z]+"[\s\S]*?;/g) ?? []).map((stmt) =>
stmt.replace(/\s+/g, ' '),
);
}
it('legt is_system_context() mit COALESCE an (ohne Variable FALSE, nicht NULL)', () => {
expect(sql).toContain('CREATE OR REPLACE FUNCTION is_system_context() RETURNS BOOLEAN AS $$');
expect(sql).toContain("COALESCE(current_setting('app.system_context', true) = 'true', false)");
expect(sql).toContain('LANGUAGE sql STABLE');
});
it('legt genau fuenf CREATE POLICY system_read_policy an, je eine fuer die fuenf Tabellen', () => {
const stmts = policyStatements(sql);
expect(stmts).toHaveLength(5);
for (const table of SYSTEM_READ_TABLES) {
const forTable = stmts.filter((stmt) => stmt.startsWith(`CREATE POLICY system_read_policy ON "${table}"`));
expect(forTable, table).toHaveLength(1);
}
});
it('jede system_read_policy ist FOR SELECT mit USING (is_system_context())', () => {
const stmts = policyStatements(sql);
expect(stmts).toHaveLength(5);
for (const stmt of stmts) {
expect(stmt).toContain('FOR SELECT');
expect(stmt).toContain('USING (is_system_context())');
expect(stmt).not.toContain('WITH CHECK');
}
});
it('enthaelt kein DROP POLICY (bestehende Regeln bleiben unveraendert)', () => {
expect(nonCommentLines(sql)).not.toContain('DROP POLICY');
});
it('enthaelt KEINE Anweisung auf SmtpConfig/Tenant/Tender ausserhalb von Kommentaren', () => {
const codeOnly = nonCommentLines(sql);
expect(codeOnly).not.toContain('SmtpConfig');
for (const table of NOT_OPENED_TABLES) {
expect(codeOnly).not.toContain(`"${table}"`);
}
});
it('nennt SmtpConfig im Kopf als bewusst nicht enthalten (Startpfad entfernt, nicht umgestellt)', () => {
expect(sql).toContain('Keine Regel auf SmtpConfig');
expect(sql).toContain('ENTFERNT');
});
});
describe('add_group_internal_name_and_object_guid migration.sql (D-04)', () => {
const sql = readMigrationSql('_add_group_internal_name_and_object_guid');