feat: complete catalog sensitivity enhancements
This commit is contained in:
@@ -9,10 +9,13 @@ import {
|
||||
} from "../catalog/types.js";
|
||||
|
||||
const idSchema = z.uuid();
|
||||
const consolidationSchema = z.object({
|
||||
target: z.enum(["tables", "columns"]),
|
||||
targetIds: z.array(idSchema).min(1).max(10_000),
|
||||
}).strict();
|
||||
const consolidationSchema = z.discriminatedUnion("target", [
|
||||
z.object({
|
||||
target: z.enum(["tables", "columns"]),
|
||||
targetIds: z.array(idSchema).min(1).max(10_000),
|
||||
}).strict(),
|
||||
z.object({ target: z.literal("database_columns") }).strict(),
|
||||
]);
|
||||
|
||||
function manage(request: FastifyRequest, reply: FastifyReply) {
|
||||
return isPrincipalContext(requirePermission(request, reply, "database.manage"));
|
||||
@@ -49,7 +52,7 @@ export function catalogDescriptionConsolidationRoutes(
|
||||
try {
|
||||
const databaseId = idSchema.parse((request.params as { databaseId?: unknown }).databaseId);
|
||||
const input = consolidationSchema.parse(request.body);
|
||||
const targetIds = [...new Set(input.targetIds)];
|
||||
const targetIds = "targetIds" in input ? [...new Set(input.targetIds)] : [];
|
||||
const result = await deps.operations.run(
|
||||
databaseId,
|
||||
async () => await deps.repository.consolidateGeneratedDescriptions(
|
||||
|
||||
@@ -19,8 +19,14 @@ const metadataSchema = z.object({
|
||||
description: z.string().max(20_000).nullable().optional(),
|
||||
generatedDescription: z.string().max(20_000).nullable().optional(),
|
||||
sensitive: z.boolean().optional(),
|
||||
sensitivityReason: z.string().max(2_000).nullable().optional(),
|
||||
}).strict().refine((value) => (
|
||||
"description" in value || "generatedDescription" in value || "sensitive" in value
|
||||
"description" in value
|
||||
|| "generatedDescription" in value
|
||||
|| "sensitive" in value
|
||||
|| "sensitivityReason" in value
|
||||
)).refine((value) => (
|
||||
value.sensitivityReason == null || value.sensitive === true
|
||||
));
|
||||
const createRunSchema = z.object({
|
||||
version: z.number().int().positive(),
|
||||
@@ -56,7 +62,10 @@ function safeError(reply: FastifyReply, error: unknown) {
|
||||
return reply.code(409).send({ code: "schema_sync_conflict", message: error.message });
|
||||
}
|
||||
if (error instanceof CatalogConnectorError) {
|
||||
return reply.code(502).send({ code: "schema_introspection_failed", message: "The database schema could not be read safely." });
|
||||
return reply.code(502).send({
|
||||
code: "schema_introspection_failed",
|
||||
message: "The database schema could not be read. Check the connection and credentials, then try again.",
|
||||
});
|
||||
}
|
||||
if (error instanceof z.ZodError) {
|
||||
return reply.code(400).send({ code: "schema_request_invalid", message: "Schema request is invalid." });
|
||||
@@ -113,6 +122,12 @@ export function catalogSchemaRoutes(
|
||||
if (current.version !== input.version) {
|
||||
return reply.code(409).send({ code: "column_stale", message: "Column metadata changed. Reload and try again." });
|
||||
}
|
||||
const nextSensitive = input.sensitive ?? current.sensitive;
|
||||
const nextSensitivityReason = nextSensitive
|
||||
? ("sensitivityReason" in input
|
||||
? normalized(input.sensitivityReason ?? null)
|
||||
: current.sensitivityReason)
|
||||
: null;
|
||||
const updated = await deps.repository.updateColumnMetadata(
|
||||
databaseId,
|
||||
tableId,
|
||||
@@ -123,6 +138,7 @@ export function catalogSchemaRoutes(
|
||||
? normalized(input.generatedDescription ?? null)
|
||||
: current.generatedDescription,
|
||||
input.sensitive,
|
||||
nextSensitivityReason,
|
||||
);
|
||||
if (!updated) return reply.code(409).send({ code: "column_stale", message: "Column metadata changed. Reload and try again." });
|
||||
return updated;
|
||||
|
||||
@@ -16,23 +16,33 @@ import {
|
||||
type WorkspaceDescriptor,
|
||||
} from "../workspaces/schema.js";
|
||||
import type { RuntimeBindings } from "../workspaces/runtime-renderer.js";
|
||||
import type { ConnectorDiagnostics } from "../workspaces/diagnostics.js";
|
||||
import type {
|
||||
ConnectorDiagnostics,
|
||||
Diagnostic,
|
||||
WorkspaceDiagnosticOptions,
|
||||
} from "../workspaces/diagnostics.js";
|
||||
import { isPrincipalContext, requirePermission } from "../auth/authorization.js";
|
||||
import type { AuthDiagnoser } from "../auth/diagnostics.js";
|
||||
import { decodeAuthDiagnostics, type AuthDiagnostics } from "../auth/group-catalog.js";
|
||||
import type { WorkspaceDatabase } from "../catalog/types.js";
|
||||
|
||||
export type WorkspaceDiagnoser = (
|
||||
workspace: WorkspaceDescriptor,
|
||||
bindings: RuntimeBindings,
|
||||
options: { writeProbe: boolean },
|
||||
options: WorkspaceDiagnosticOptions,
|
||||
) => Promise<ConnectorDiagnostics>;
|
||||
|
||||
export type WorkspaceDatabaseTester = (
|
||||
workspaceId: string,
|
||||
) => Promise<WorkspaceDatabase | undefined>;
|
||||
|
||||
interface WorkspaceRoutesDeps {
|
||||
registry: WorkspaceRegistry;
|
||||
config: WorkspaceRegistryConfig;
|
||||
diagnose: WorkspaceDiagnoser;
|
||||
authDiagnoser: AuthDiagnoser;
|
||||
secretStore: WorkspaceSecretStore;
|
||||
testDatabaseConnection: WorkspaceDatabaseTester;
|
||||
}
|
||||
|
||||
const workspaceId = z.string().regex(/^[a-z][a-z0-9-]{2,62}$/);
|
||||
@@ -56,6 +66,20 @@ const SAFE_MESSAGES = {
|
||||
semantic_index_incompatible: "Semantic index is incompatible with this workspace.",
|
||||
} as const;
|
||||
|
||||
const catalogConnectionUnavailable = (): Diagnostic => ({
|
||||
level: "error",
|
||||
code: "connector_unavailable",
|
||||
field: "dwh",
|
||||
message: "The configured database could not be reached or authenticated.",
|
||||
});
|
||||
|
||||
const catalogConnectionMissing = (): Diagnostic => ({
|
||||
level: "error",
|
||||
code: "binding_missing",
|
||||
field: "dwh",
|
||||
message: "Configure this workspace in Database Management before testing connections.",
|
||||
});
|
||||
|
||||
function authenticationReport(value: unknown): AuthDiagnostics {
|
||||
const report = decodeAuthDiagnostics(value);
|
||||
if (!report) throw new Error("invalid authentication diagnostic report");
|
||||
@@ -238,14 +262,33 @@ export function workspaceRoutes(app: FastifyInstance, deps: WorkspaceRoutesDeps)
|
||||
deps.secretStore,
|
||||
);
|
||||
try {
|
||||
const [workspaceDiagnostics, inspectedAuthentication] = await Promise.all([
|
||||
deps.diagnose(operational, lease.bindings, { writeProbe: false }),
|
||||
const [workspaceDiagnostics, testedDatabase, inspectedAuthentication] = await Promise.all([
|
||||
deps.diagnose(operational, lease.bindings, {
|
||||
writeProbe: false,
|
||||
skipDwh: true,
|
||||
}),
|
||||
deps.testDatabaseConnection(id),
|
||||
deps.authDiagnoser.inspect({ live: true }),
|
||||
]);
|
||||
const authentication = authenticationReport(inspectedAuthentication);
|
||||
const catalogConnectionReady = testedDatabase?.connectionStatus === "reachable";
|
||||
const catalogConnectionDiagnostic = !testedDatabase
|
||||
? catalogConnectionMissing()
|
||||
: catalogConnectionReady
|
||||
? undefined
|
||||
: catalogConnectionUnavailable();
|
||||
const diagnostics = catalogConnectionDiagnostic
|
||||
? [
|
||||
...workspaceDiagnostics.diagnostics.filter(({ code }) => code !== "binding_ok"),
|
||||
catalogConnectionDiagnostic,
|
||||
]
|
||||
: workspaceDiagnostics.diagnostics;
|
||||
return {
|
||||
...workspaceDiagnostics,
|
||||
activatable: workspaceDiagnostics.activatable && authentication.ready,
|
||||
activatable: workspaceDiagnostics.activatable
|
||||
&& catalogConnectionReady
|
||||
&& authentication.ready,
|
||||
diagnostics,
|
||||
authentication,
|
||||
};
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user