feat(datasources): enforce policy-aware data visibility
Module Package Release / publish-packages (push) Successful in 11s

This commit is contained in:
2026-08-21 20:31:52 +02:00
parent 97ca670bfe
commit b54d1919e4
18 changed files with 1675 additions and 72 deletions
+3
View File
@@ -28,6 +28,8 @@ export type DatasourceGovernance = {
classification: string;
privacy_profile_ref?: string | null;
retention_policy_ref?: string | null;
access_policy_ref?: string | null;
visibility_policy: Record<string, unknown>;
hold_refs: string[];
publication_state: string;
transfer_agreement_ref?: string | null;
@@ -43,6 +45,7 @@ export type DatasourceField = {
name: string;
data_type: string;
nullable: boolean;
classification: string;
};
export type Datasource = {
@@ -73,6 +73,7 @@ import {
import {
DATASOURCE_FIELDS_DOCUMENTATION,
DATASOURCE_GOVERNANCE_DOCUMENTATION,
DATASOURCE_VISIBILITY_DOCUMENTATION,
DATASOURCES_DOCUMENTATION,
DATASOURCES_I18N
} from "./interfacePatterns";
@@ -898,6 +899,7 @@ function GovernanceDialog({
const [draft, setDraft] = useState<DatasourceGovernance | null>(null);
const [freshness, setFreshness] = useState("{}");
const [quality, setQuality] = useState("{}");
const [visibility, setVisibility] = useState("{}");
const [baselineKey, setBaselineKey] = useState("");
const [busy, setBusy] = useState(false);
const [error, setError] = useState("");
@@ -908,14 +910,16 @@ function GovernanceDialog({
const nextDraft = structuredClone(datasource.governance);
const nextFreshness = JSON.stringify(datasource.governance.freshness_policy, null, 2);
const nextQuality = JSON.stringify(datasource.governance.quality_policy, null, 2);
const nextVisibility = JSON.stringify(datasource.governance.visibility_policy ?? {}, null, 2);
setDraft(nextDraft);
setFreshness(nextFreshness);
setQuality(nextQuality);
setBaselineKey(JSON.stringify({ draft: nextDraft, freshness: nextFreshness, quality: nextQuality }));
setVisibility(nextVisibility);
setBaselineKey(JSON.stringify({ draft: nextDraft, freshness: nextFreshness, quality: nextQuality, visibility: nextVisibility }));
setError("");
}, [datasource, open]);
const dirty = Boolean(open && draft && JSON.stringify({ draft, freshness, quality }) !== baselineKey);
const dirty = Boolean(open && draft && JSON.stringify({ draft, freshness, quality, visibility }) !== baselineKey);
const save = async (): Promise<boolean> => {
if (!datasource || !draft) return false;
@@ -925,7 +929,8 @@ function GovernanceDialog({
const updated = await updateDatasourceGovernance(settings, datasource.ref, {
...draft,
freshness_policy: parseObject(freshness, "Freshness policy"),
quality_policy: parseObject(quality, "Quality policy")
quality_policy: parseObject(quality, "Quality policy"),
visibility_policy: parseObject(visibility, "Visibility policy")
});
await onSaved(updated);
return true;
@@ -1020,6 +1025,9 @@ function GovernanceDialog({
<FormField label="Retention policy" interfaceId="datasources.field.retention-policy" helpContextId="datasources.field.retention-policy" helpModuleId="datasources" documentation={DATASOURCE_GOVERNANCE_DOCUMENTATION}>
<input value={draft.retention_policy_ref ?? ""} onChange={(event) => setValue("retention_policy_ref", event.target.value || null)} />
</FormField>
<FormField label="Access policy reference" interfaceId="datasources.field.access-policy" helpContextId="datasources.field.access-policy" helpModuleId="datasources" documentation={DATASOURCE_VISIBILITY_DOCUMENTATION}>
<input value={draft.access_policy_ref ?? ""} onChange={(event) => setValue("access_policy_ref", event.target.value || null)} placeholder="Optional Policy module target" />
</FormField>
<FormField label="Transfer agreement" interfaceId="datasources.field.transfer-agreement" helpContextId="datasources.field.transfer-agreement" helpModuleId="datasources" documentation={DATASOURCE_GOVERNANCE_DOCUMENTATION}>
<input value={draft.transfer_agreement_ref ?? ""} onChange={(event) => setValue("transfer_agreement_ref", event.target.value || null)} />
</FormField>
@@ -1040,6 +1048,10 @@ function GovernanceDialog({
<GovernanceListField label="Known limits" values={draft.known_limits} onChange={(values) => setValue("known_limits", values)} />
</FormGrid>
<FormGrid columns={2} gap="small" collapseAt="narrow">
<FormField label="Visibility policy (JSON)" interfaceId="datasources.field.visibility-policy" helpContextId="datasources.field.visibility-policy" helpModuleId="datasources" documentation={DATASOURCE_VISIBILITY_DOCUMENTATION}>
<textarea value={visibility} onChange={(event) => setVisibility(event.target.value)} spellCheck={false} />
<small>Configure source and materialization ACLs, field redaction or omission, and principal-bound row filters.</small>
</FormField>
<FormField label="Freshness policy (JSON)" documentation={DATASOURCE_GOVERNANCE_DOCUMENTATION}>
<textarea value={freshness} onChange={(event) => setFreshness(event.target.value)} spellCheck={false} />
</FormField>
@@ -1463,17 +1475,18 @@ function SchemaTable({ fields }: { fields: Datasource["schema"] }) {
<div className="datasources-table-scroll">
<table>
<thead>
<tr><th>Field</th><th>Type</th><th>Nullable</th></tr>
<tr><th>Field</th><th>Type</th><th>Classification</th><th>Nullable</th></tr>
</thead>
<tbody>
{fields.map((field) => (
<tr key={field.name}>
<td>{field.name}</td>
<td>{field.data_type}</td>
<td>{readableToken(field.classification)}</td>
<td>{field.nullable ? "Yes" : "No"}</td>
</tr>
))}
{!fields.length ? <tr><td colSpan={3}>Schema not available</td></tr> : null}
{!fields.length ? <tr><td colSpan={4}>Schema not available</td></tr> : null}
</tbody>
</table>
</div>
@@ -15,6 +15,11 @@ export const DATASOURCE_GOVERNANCE_DOCUMENTATION = {
documentationType: "admin"
} satisfies DocumentationHelpReference;
export const DATASOURCE_VISIBILITY_DOCUMENTATION = {
topicId: "datasources.visibility",
documentationType: "admin"
} satisfies DocumentationHelpReference;
export const DATASOURCES_I18N = {
loading: "i18n:govoplan-datasources.loading_reason",
working: "i18n:govoplan-datasources.working_reason",
+8
View File
@@ -63,6 +63,10 @@ const en = {
"Semantic definition": "Semantic definition",
"Freshness policy (JSON)": "Freshness policy (JSON)",
"Quality policy (JSON)": "Quality policy (JSON)",
"Access policy reference": "Access policy reference",
"Optional Policy module target": "Optional Policy module target",
"Visibility policy (JSON)": "Visibility policy (JSON)",
"Configure source and materialization ACLs, field redaction or omission, and principal-bound row filters.": "Configure source and materialization ACLs, field redaction or omission, and principal-bound row filters.",
"Quality policy": "Quality policy",
"Policy hash": "Policy hash",
"Schema change": "Schema change",
@@ -136,6 +140,10 @@ const de: Record<keyof typeof en, string> = {
"Semantic definition": "Semantische Definition",
"Freshness policy (JSON)": "Aktualitätsrichtlinie (JSON)",
"Quality policy (JSON)": "Qualitätsrichtlinie (JSON)",
"Access policy reference": "Zugriffsrichtlinienreferenz",
"Optional Policy module target": "Optionales Ziel im Richtlinienmodul",
"Visibility policy (JSON)": "Sichtbarkeitsrichtlinie (JSON)",
"Configure source and materialization ACLs, field redaction or omission, and principal-bound row filters.": "Konfigurieren Sie Zugriffslisten für Quellen und Materialisierungen, Feldschwärzung oder -auslassung sowie an den Akteur gebundene Zeilenfilter.",
"Quality policy": "Qualitätsrichtlinie",
"Policy hash": "Richtlinien-Hash",
"Schema change": "Schemaänderung",