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
@@ -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>