feat: protect sensitive catalog samples

This commit is contained in:
Codex
2026-08-30 12:14:23 +02:00
parent 6278ee9d81
commit 0736983bc5
28 changed files with 1162 additions and 128 deletions
@@ -10,6 +10,7 @@ import { up as upTables } from "../src/catalog/migrations/002_catalog_tables.js"
import { up as upSchemaSync } from "../src/catalog/migrations/003_catalog_schema_sync.js";
import { up as upRuntimeSequencePrivileges } from "../src/catalog/migrations/004_catalog_runtime_sequence_privileges.js";
import { up as upDescriptionGeneration } from "../src/catalog/migrations/005_description_generation_runs.js";
import { up as upSensitiveDataFlag } from "../src/catalog/migrations/006_sensitive_data_flag.js";
const dockerAvailable = spawnSync("docker", ["info"], { stdio: "ignore" }).status === 0;
@@ -23,6 +24,7 @@ test.skipIf(!dockerAvailable)("PostgreSQL migration enforces one database per wo
await upDatabases(db);
await upTables(db);
await upSchemaSync(db);
await upSensitiveDataFlag(db);
await sql`CREATE ROLE thothii_catalog_runtime`.execute(db);
await upRuntimeSequencePrivileges(db);
const sequencePrivilege = await sql<{ allowed: boolean }>`
@@ -75,11 +77,47 @@ test.skipIf(!dockerAvailable)("PostgreSQL migration enforces one database per wo
};
expect(await repository.applySchemaSync(created.id, 1, "columns", [], fullColumnsSnapshot))
.toMatchObject({ created: 4, deleted: 0 });
expect((await repository.listColumns(created.id, patients.id)).map((column) => column.name))
.toEqual(["id", "name"]);
expect((await repository.listColumns(created.id, patients.id)).map((column) => ({
name: column.name,
sensitive: column.sensitive,
}))).toEqual([
{ name: "id", sensitive: false },
{ name: "name", sensitive: false },
]);
expect((await repository.listColumns(created.id, visits.id)).map((column) => column.name))
.toEqual(["id", "patient_id"]);
const patientName = (await repository.listColumns(created.id, patients.id))
.find((column) => column.name === "name")!;
expect(await repository.updateColumnMetadata(
created.id,
patients.id,
patientName.id,
patientName.version,
patientName.description,
patientName.generatedDescription,
true,
)).toMatchObject({ sensitive: true });
const refreshedColumnsSnapshot: ObservedSchemaSnapshot = {
...fullColumnsSnapshot,
schemaVersion: 2,
columns: fullColumnsSnapshot.columns.map((column) => column.tableName === "patients"
&& column.name === "name"
? { ...column, sourceComment: "Sensitive patient name" }
: column),
};
expect(await repository.applySchemaSync(
created.id,
1,
"columns",
[patients.id],
refreshedColumnsSnapshot,
)).toMatchObject({ updated: 1 });
expect(await repository.getColumn(created.id, patients.id, patientName.id)).toMatchObject({
sensitive: true,
sourceComment: "Sensitive patient name",
});
const reducedColumnsSnapshot: ObservedSchemaSnapshot = {
...fullColumnsSnapshot,
columns: fullColumnsSnapshot.columns.filter((column) => column.name === "id"),
@@ -135,6 +173,7 @@ test.skipIf(!dockerAvailable)("PostgreSQL repository performs scoped metadata cl
await upDatabases(db);
await upTables(db);
await upSchemaSync(db);
await upSensitiveDataFlag(db);
const repository = new KyselyCatalogRepository(db);
const database = await repository.create({
workspaceId: "cleanup-test",
@@ -212,6 +251,7 @@ test.skipIf(!dockerAvailable)("PostgreSQL repository atomically consolidates sel
await upDatabases(db);
await upTables(db);
await upSchemaSync(db);
await upSensitiveDataFlag(db);
const repository = new KyselyCatalogRepository(db);
const database = await repository.create({
workspaceId: "consolidation-test",
@@ -310,6 +350,7 @@ test.skipIf(!dockerAvailable)("PostgreSQL repository persists globally exclusive
await upDatabases(db);
await upTables(db);
await upSchemaSync(db);
await upSensitiveDataFlag(db);
await upDescriptionGeneration(db);
const repository = new KyselyCatalogRepository(db);
const firstDatabase = await repository.create({