feat: consolidate database management work
Add catalog-owned logical relationships and runtime snapshots, extend the database-management UI and validation coverage, and document the updated operational workflow. Keep active sensitive-generation status in a tooltip and indicator, and update the layout E2E to follow the history action in its new database-scoped location.
This commit is contained in:
@@ -2,6 +2,7 @@ import { buildInstallationContract } from "../workspaces/contracts.js";
|
||||
import { resolveBinding } from "../workspaces/bindings.js";
|
||||
import type { WorkspaceRegistry } from "../workspaces/registry.js";
|
||||
import type { WorkspaceDescriptor } from "../workspaces/schema.js";
|
||||
import { discoverWorkspaceSecretRequirements } from "../workspaces/secret-requirements.js";
|
||||
import type { WorkspaceSecretStore } from "../workspaces/secret-store.js";
|
||||
import { createConcreteDiagnosticAdapters } from "../workspaces/diagnostics.js";
|
||||
import { CatalogOperationCoordinator } from "./operation-coordinator.js";
|
||||
@@ -25,6 +26,21 @@ export interface CatalogListItem extends Omit<WorkspaceDatabase, "id"> {
|
||||
workspaceName: string;
|
||||
workspaceDescription?: string;
|
||||
workspaceAvailable: boolean;
|
||||
workspaceRevision: { commit: string; blob: string } | null;
|
||||
workspaceEvidence: {
|
||||
sourceType: "filesystem" | "http" | "s3" | null;
|
||||
state:
|
||||
| "not_declared"
|
||||
| "materialized_current_revision"
|
||||
| "configuration_required"
|
||||
| "configured_unverified"
|
||||
| "workspace_unavailable";
|
||||
};
|
||||
runtimeBinding: {
|
||||
transport: DatabaseBinding["transport"];
|
||||
configurationState: "ready" | "configuration_required";
|
||||
sessionTransportSupported: boolean;
|
||||
} | null;
|
||||
configured: boolean;
|
||||
secrets: Record<CatalogSecretName, boolean>;
|
||||
}
|
||||
@@ -68,6 +84,43 @@ function secretState(store: WorkspaceSecretStore, workspaceId: string): Record<C
|
||||
))) as Record<CatalogSecretName, boolean>;
|
||||
}
|
||||
|
||||
function workspaceRuntimeState(
|
||||
store: WorkspaceSecretStore,
|
||||
workspace: WorkspaceDescriptor,
|
||||
secretRoots: readonly string[],
|
||||
): NonNullable<CatalogListItem["runtimeBinding"]> {
|
||||
const requirements = discoverWorkspaceSecretRequirements(workspace, process.env);
|
||||
const secretVariables = new Set(requirements.map(({ variable }) => variable));
|
||||
const effective = resolveBinding(workspace, "DWH", process.env, secretRoots);
|
||||
const configurationRequired = requirements.some(({ id, required }) => (
|
||||
required && !store.has(workspace.workspace.id, id)
|
||||
)) || effective.missing.some((variable) => !secretVariables.has(variable));
|
||||
return {
|
||||
transport: effective.transport,
|
||||
configurationState: configurationRequired ? "configuration_required" : "ready",
|
||||
sessionTransportSupported: effective.transport !== "ssh_tunnel",
|
||||
};
|
||||
}
|
||||
|
||||
function workspaceEvidenceState(
|
||||
store: WorkspaceSecretStore,
|
||||
workspace: WorkspaceDescriptor,
|
||||
): CatalogListItem["workspaceEvidence"] {
|
||||
const source = workspace.evidence?.source;
|
||||
if (!source) return { sourceType: null, state: "not_declared" };
|
||||
if (source.type === "filesystem") {
|
||||
return { sourceType: source.type, state: "materialized_current_revision" };
|
||||
}
|
||||
const configurationRequired = discoverWorkspaceSecretRequirements(workspace, process.env)
|
||||
.some(({ connector, id, required }) => (
|
||||
connector === "evidence" && required && !store.has(workspace.workspace.id, id)
|
||||
));
|
||||
return {
|
||||
sourceType: source.type,
|
||||
state: configurationRequired ? "configuration_required" : "configured_unverified",
|
||||
};
|
||||
}
|
||||
|
||||
export class CatalogService {
|
||||
private readonly adapters = createConcreteDiagnosticAdapters();
|
||||
|
||||
@@ -91,7 +144,8 @@ export class CatalogService {
|
||||
const byWorkspace = new Map(configured.map((database) => [database.workspaceId, database]));
|
||||
const active = await Promise.all(workspaces.map(async (entry) => {
|
||||
const database = byWorkspace.get(entry.id);
|
||||
const { workspace } = await this.registry.read(entry.id);
|
||||
const { workspace } = await this.registry.readPinned(entry.id, entry.revision.commit);
|
||||
const runtimeDatabaseBinding = yamlBinding(workspace, this.secretRoots);
|
||||
const base = database ?? {
|
||||
workspaceId: entry.id,
|
||||
engine: "postgres" as const,
|
||||
@@ -100,7 +154,7 @@ export class CatalogService {
|
||||
version: 0,
|
||||
createdAt: "",
|
||||
updatedAt: "",
|
||||
binding: yamlBinding(workspace, this.secretRoots),
|
||||
binding: runtimeDatabaseBinding,
|
||||
connectionStatus: "untested" as const,
|
||||
};
|
||||
return {
|
||||
@@ -108,6 +162,12 @@ export class CatalogService {
|
||||
workspaceName: entry.name,
|
||||
workspaceDescription: entry.description,
|
||||
workspaceAvailable: true,
|
||||
workspaceRevision: {
|
||||
commit: entry.revision.commit,
|
||||
blob: entry.revision.blob,
|
||||
},
|
||||
workspaceEvidence: workspaceEvidenceState(this.secretStore, workspace),
|
||||
runtimeBinding: workspaceRuntimeState(this.secretStore, workspace, this.secretRoots),
|
||||
configured: database !== undefined,
|
||||
secrets: secretState(this.secretStore, entry.id),
|
||||
};
|
||||
@@ -120,6 +180,9 @@ export class CatalogService {
|
||||
workspaceName: database.workspaceId,
|
||||
workspaceDescription: "Workspace is no longer present in the repository catalog.",
|
||||
workspaceAvailable: false,
|
||||
workspaceRevision: null,
|
||||
workspaceEvidence: { sourceType: null, state: "workspace_unavailable" },
|
||||
runtimeBinding: null,
|
||||
configured: true,
|
||||
secrets: secretState(this.secretStore, database.workspaceId),
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user