Skip to content

Unity Catalog Governance Patterns

An ERP's own security model doesn't land with its data — so what do you rebuild? Five patterns in plain SQL — layout, ownership, fine-grained access, tags, lineage — then eight ERP sections naming what to scope and what to mask.

Verified August 2026

Databricks capability claims below are current to the date above and release status of preview features changes — verify against current Databricks documentation before you build.

The ERP's security model doesn't land with its data

Whatever the source system was enforcing stops at the extract boundary. SAP authorization objects don't travel with a replicated table. Oracle EBS multi-org access control is a runtime filter applied by the application, and this library's own EBS reference already states the consequence plainly: it never applies to a lakehouse extract. NetSuite's role-based access decides what a login sees in NetSuite, not what a landed table holds. What you have is every client, every company, and every column, sitting in one place, with exactly the grants you wrote.

The goal, stated as an opinion rather than a principle: least privilege that analysts don't feel. Humans read gold. Pipelines write bronze. The handful of genuinely sensitive columns are masked by exception, in place — never copied into a parallel “secure” schema that has to be kept in sync forever and silently won't be.

Everything on this page is plain SQL against Unity Catalog. No framework, no governance product, no orchestration layer. Five patterns, five SQL blocks, and a per-reference list of what to scope and what to mask.

This page has a sibling. The ERP data quality checklist is the set of checks that make the numbers right; this one is the set of controls that make them safe to open up. They lean on the same column in every ERP, for different reasons.

Pattern 1 — one catalog per environment, one schema per layer per source

Environments are catalogs. supply_chain_prod and supply_chain_dev — never a name suffix inside one catalog. Two things follow. Promoting a pipeline then rewrites a catalog name and nothing else, so no schema or table name ever has to change; and a production grant cannot leak into development, because they are different securables under different parents. Where storage has to be isolated too, a catalog can carry its own managed location.

Layers crossed with sources are schemas. bronze_sap, bronze_jde, bronze_netsuite— the shape this site's own SAP boilerplate already assumes when it reads bronze_sap.mseg. Silver stays per-source, because silver is cleaned, conformed, still source-aligned entities. Gold is one schema, because the star schemas are the cross-source product and a conformed dimension has no source system to file it under.

The reason this grain is right is a grants argument. A privilege granted on a schema inherits to its current and futuretables. So one GRANT SELECT on a schema covers a source's whole layer forever, including the table the pipeline creates next quarter — and the alternative, per-table grants, is a maintenance burden that quietly rots into over-granting.

One practical note: every table page in this library parameterizes catalog and schema in its copy-SQL, so any convention works downstream. Pick one and hold it — the cost is in changing it, not in choosing it.

-- Pattern 1: environments are catalogs, layers-per-source are schemas.
-- Nothing here is clever. The value is that it never changes again: a schema
-- grant inherits to every table the pipeline creates later.
CREATE CATALOG IF NOT EXISTS supply_chain_prod
  COMMENT 'Production supply chain lakehouse. Dev is its own catalog.';

-- Bronze: one schema per source system, shaped the way that source publishes.
CREATE SCHEMA IF NOT EXISTS supply_chain_prod.bronze_sap
  COMMENT 'Raw SAP tables as landed — MANDT intact, DATS strings uncast.';
CREATE SCHEMA IF NOT EXISTS supply_chain_prod.bronze_jde
  COMMENT 'Raw JD Edwards tables as landed — Julian dates and implied decimals uncast.';
CREATE SCHEMA IF NOT EXISTS supply_chain_prod.bronze_netsuite
  COMMENT 'Raw NetSuite2.com tables as landed via SuiteAnalytics Connect.';

-- Silver stays per-source: cleaned, conformed, still source-aligned entities.
CREATE SCHEMA IF NOT EXISTS supply_chain_prod.silver_sap
  COMMENT 'Cleaned SAP entities. Conversions happen here, once.';
CREATE SCHEMA IF NOT EXISTS supply_chain_prod.silver_jde
  COMMENT 'Cleaned JD Edwards entities.';
CREATE SCHEMA IF NOT EXISTS supply_chain_prod.silver_netsuite
  COMMENT 'Cleaned NetSuite entities.';

-- Gold is ONE schema, not one per source: the star schemas are the cross-source
-- product, and a conformed dimension has no source system to file it under.
CREATE SCHEMA IF NOT EXISTS supply_chain_prod.gold
  COMMENT 'Conformed dimensions and facts. The layer humans read.';

-- Dev is a separate catalog, never a suffix inside this one — promoting a
-- pipeline then rewrites a catalog name and nothing else, and a prod grant
-- cannot leak into dev. Add MANAGED LOCATION on the catalog when the
-- environments have to sit on separate storage.

Pattern 2 — pipelines own the write path, analysts get gold

Three principals. A pipeline service principal per source, which writes bronze and silver — USE SCHEMA, SELECT, MODIFY, CREATE TABLE. An engineers group, which reads silver; bronze access is a time-boxed exception granted for an investigation and revoked after it, never a standing grant. An analysts group, which reads gold and nothing else.

Grant to groups, own with groups. Grants go to groups, never to individual users — that is the documented recommendation and also the only version that survives a reorganization. Ownership follows the same rule for a blunter reason: a securable owned by a person breaks the day that person leaves. And day-to-day grant administration can be delegated with MANAGE, without transferring ownership at all, which is the right answer for a steward who approves access requests but shouldn't be able to drop the schema.

Two traps worth naming. First, SELECT without USE CATALOG and USE SCHEMA reaches nothing — traversal privileges are separate, and this is the reason most “I granted it and they still can't see it” tickets exist. Second, there is no DENY. Absence of a grant is the deny, and an inherited catalog- or schema-level grant cannot be overridden on one table; it can only be narrowed at the parent. So never grant broad intending to claw back — the claw-back doesn't exist.

Bronze is closed. Not masked, closed. It is the simpler alternative to maintaining a second set of policies over raw, unconverted, source-shaped tables, and it removes an entire class of question from every per-ERP section below.

-- Pattern 2: three principals, and the write path is not one of the human ones.
-- Traversal first, or nothing below it is reachable: SELECT alone reaches
-- exactly nothing without USE CATALOG and USE SCHEMA.
GRANT USE CATALOG ON CATALOG supply_chain_prod TO `svc_pipeline_sap`;
GRANT USE SCHEMA, SELECT, MODIFY, CREATE TABLE
  ON SCHEMA supply_chain_prod.bronze_sap TO `svc_pipeline_sap`;
GRANT USE SCHEMA, SELECT, MODIFY, CREATE TABLE
  ON SCHEMA supply_chain_prod.silver_sap TO `svc_pipeline_sap`;

-- Engineers read silver. Bronze access is a time-boxed exception granted for an
-- investigation and revoked after it, never a standing grant.
GRANT USE CATALOG ON CATALOG supply_chain_prod TO `data_engineers`;
GRANT USE SCHEMA, SELECT ON SCHEMA supply_chain_prod.silver_sap TO `data_engineers`;

-- Analysts read gold, and only gold. One grant covers every table the star
-- schema grows later, because a schema privilege inherits to future tables.
GRANT USE CATALOG ON CATALOG supply_chain_prod TO `analysts`;
GRANT USE SCHEMA, SELECT ON SCHEMA supply_chain_prod.gold TO `analysts`;

-- Own with a group, never a person: ownership held by an individual breaks the
-- day that individual leaves.
ALTER SCHEMA supply_chain_prod.gold OWNER TO `platform_team`;

-- Delegate day-to-day grant administration without handing over ownership.
GRANT MANAGE ON SCHEMA supply_chain_prod.gold TO `gold_stewards`;

-- There is no DENY. Absence of a grant IS the deny, and an inherited catalog-
-- or schema-level grant cannot be overridden on one table — it can only be
-- narrowed at the parent. So never grant broad intending to claw back.

Pattern 3 — row filters on the org column, masks on the columns the references flag

The two mechanisms. A row filter is a BOOLEAN user-defined function attached to a table with ALTER TABLE … SET ROW FILTER … ON (col): rows for which it returns false are not there, as far as the reader is concerned. A column mask is a UDF attached with ALTER TABLE … ALTER COLUMN … SET MASK, transforming the value on the way out. Both are generally available.

The ERP twist.You mostly don't have to design either binding, but you do have to pick the right column. Bind the filter to the ERP's org column — the one that says which part of the business a row belongs to. Often that is the same column the data quality checklist already tells you to pin in every join and group-by: DATAAREAID, CONO, ORG_ID, LGNUM. Sometimes it isn't. That list is a correctness list, not an entitlement list, and two references come apart on the difference — SAP, where the client key keeps two tenants from summing together but says nothing about who may read a company's data, and NetSuite, where subsidiary scopes correctly and still leaves the finance documents on the spine visible. Each section below names the column that actually entitles. And the columns worth a mask are already documented, table by table, in this library's own references.

Four facts to build on. Filters and masks govern every reader on every path — notebook, SQL editor, dashboard, Genie — unlike a dynamic view, which only governs the readers who come through the view. The policy function runs with the table owner's authority, so a reader never needs access to whatever the function itself reads. Identity resolves through is_account_group_member(), which evaluates against account-level groups rather than workspace-local ones. And grants come first: a mask narrows what SELECT returns, it never grants — group membership without SELECT gets a reader nothing.

One limitation to check. Tables carrying table-level filters or masks can't be shared out through Delta Sharing by open-sharing providers. Behind the dated banner at the top of this page: check the current limitations list before attaching policies to a table you also share externally.

Where to attach them. Silver and gold. Not bronze — bronze is closed instead, which is pattern 2 and is why this section has nothing to say about raw tables.

-- Pattern 3a: a row filter is a BOOLEAN UDF bound to the table's org column.
-- Bind it to the ERP's org column — the one that says which part of the
-- business a row belongs to. Dynamics 365 F&O below; substitute CONO, ORG_ID,
-- or LGNUM, and read the SAP and NetSuite sections before assuming the column
-- you pinned for correctness is the one that entitles.
CREATE OR REPLACE FUNCTION supply_chain_prod.gold.company_row_filter(dataareaid STRING)
  RETURN is_account_group_member('all_companies')
      OR is_account_group_member(concat('company_', lower(dataareaid)));

ALTER TABLE supply_chain_prod.gold.fct_inventory_txn
  SET ROW FILTER supply_chain_prod.gold.company_row_filter ON (dataareaid);

-- Pattern 3b: a column mask is a UDF over the column's own value. It returns
-- the value to the named audience and something harmless to everyone else.
CREATE OR REPLACE FUNCTION supply_chain_prod.gold.cost_mask(cost DECIMAL(19,4))
  RETURN CASE WHEN is_account_group_member('costing_readers') THEN cost ELSE NULL END;

ALTER TABLE supply_chain_prod.gold.fct_inventory_txn
  ALTER COLUMN unit_cost SET MASK supply_chain_prod.gold.cost_mask;

-- Three things to hold on to:
--   1. The policy function runs with the table owner's authority, so the
--      reader never needs access to whatever the function itself reads.
--   2. Filters and masks govern EVERY reader on every path — notebook, SQL
--      editor, dashboard, Genie. A view only governs whoever comes through it.
--   3. Grants come first. A mask narrows what SELECT returns; it never grants.
--      Membership of costing_readers gets a reader nothing without SELECT.

Pattern 4 — a small tag vocabulary, then tag-driven policies

Two kinds of tag, and they are not the same thing. Freeform tags are unmanaged: anyone with the right privilege writes any key and any value, and the vocabulary drifts. Governed tags are defined at the account level — the allowed keys and values are set centrally. Assigning a tag is itself a privilege in both cases: APPLY TAG on the securable, which is what keeps the vocabulary from being widened by whoever happens to be editing a table.

The vocabulary. Exactly two keys to start: classification (internal, confidential, restricted) and source_system(sap, jde, and the rest). That is the practice's opinion, and the reason is downstream: a policy engine pointed at a sprawling vocabulary automates the wrong thing at scale, and a tag nobody can define precisely is a tag nobody should be enforcing on. Tag the specific columns the eight sections below name. The tag says what a column is; the mask enforces who sees it; they travel together and neither substitutes for the other.

-- Pattern 4: tag the column, then let the mask enforce it. Two keys only —
-- what the column is, and where it came from.
ALTER TABLE supply_chain_prod.silver_sap.lfa1
  ALTER COLUMN stcd1
  SET TAGS ('classification' = 'restricted', 'source_system' = 'sap');

Then, and only then, tag-driven policies. Attribute-based access control lets a policy target every column carrying a tag, rather than attaching a mask table by table. Databricks documents tag-based row-filter and column-mask policies as generally available and ABAC GRANT policies as beta, as of August 2026. That is the whole claim — check the current status before you build on it.

The recommendation. Start with filters and masks attached directly. Adopt tag-driven policies once the vocabulary has been stable for a few quarters — automation over a vocabulary that is still moving is how a governance program produces its first outage.

Pattern 5 — lineage and audit are queries, not documents

Where did this number come from? system.access.table_lineage and system.access.column_lineage answer it as a query: source and target full names, captured automatically as work runs, not maintained by anyone. That is the trust chain behind every KPI this library defines — and column-level lineage is specifically how you prove that a masked column never leaked into a downstream table under a different name. It answers the question inside the lakehouse only: lineage starts at bronze, and what the source kept — or overwrote — before the first table landed is the subject of the source history guide, which names the daily balances, effective-dated parameters and plan vintages the lakehouse has to snapshot for itself.

Who touched the vendor master? system.access.audit— “touched” rather than “read”, because what the log records are metadata and credential events that approximate a read rather than the read itself. It is an event stream, so filter event_date first, pin the actions your own workspace actually emits, and expect the request_params map to carry different keys for different event types. This table is also the evidence that the masking was worth building, and the only place an access review can start.

Two caveats. The system schemas have to be enabled, and access to them is not granted by default — a metastore admin grants USE CATALOG, USE SCHEMA and SELECT on system.access to the platform group. And retention is bounded: roughly a year by default, and it varies, so verify it. If compliance needs a longer horizon, copy the audit rows you care about into a table of your own on a schedule. That is a scheduled MERGE, not a product.

-- Pattern 5a: "where did this number come from" is a query, not a diagram.
-- Lineage is captured automatically; you read it, you don't maintain it.
SELECT
  source_table_full_name,
  source_type,
  MAX(event_time) AS last_seen
FROM system.access.table_lineage
WHERE target_table_full_name = 'supply_chain_prod.gold.fct_inventory_txn'
  AND event_date >= CURRENT_DATE() - INTERVAL 90 DAYS
GROUP BY source_table_full_name, source_type
ORDER BY last_seen DESC;

-- Pattern 5b: "who touched the vendor master" is the other query. This is an
-- event stream, so filter event_date first — it is what the table is
-- partitioned on, and an unfiltered scan of an audit log is an expensive way to
-- learn nothing.
SELECT
  user_identity.email AS who,
  action_name,
  event_time,
  request_params.commandText
FROM system.access.audit
WHERE event_date >= CURRENT_DATE() - INTERVAL 30 DAYS
  AND request_params.full_name_arg = 'supply_chain_prod.silver_sap.lfa1'
  -- Which actions to pin depends on which events your workspace emits, so
  -- inspect your own audit rows before fixing this list. These two approximate
  -- a read: metadata resolution and a credential handed out to read the data.
  AND action_name IN ('getTable', 'generateTemporaryTableCredential')
ORDER BY event_time DESC;

-- Two caveats before either query returns anything:
--   1. request_params is a map whose keys vary by event type — inspect the
--      keys your own events carry rather than assuming full_name_arg is set.
--   2. The system schemas have to be enabled, and access is NOT granted by
--      default. A metastore admin grants USE CATALOG, USE SCHEMA and SELECT on
--      system.access to the platform group. Retention is bounded (about a year
--      by default, and it varies) — verify it, and copy audit rows into a table
--      of your own on a schedule if compliance needs a longer horizon.

What the engine will not do — seven limits, tested

The five patterns above lean on documented behaviour. Each item below was instead verified hands-on, in one workspace on the date given, because the documentation either does not say it or says it too quietly to build on. One workspace, one date — retest anything load-bearing in yours.

There is no column-level GRANT. GRANT SELECT (col) ON TABLE fails at parse time (verified in one workspace on 2026-08-27). Restricting a column means a mask, a view, or not exposing the column — which is why pattern 3 exists.

A masked-column inventory reads three surfaces. A column mask created through a tag-driven policy showed up in neither information_schema.column_masks nor DESCRIBE EXTENDED (verified in one workspace on 2026-08-27, a single unreplicated run). An audit that reads only those two surfaces can certify a column open that is in fact masked — read the policy definitions too.

The owner is not exempt, and views do not launder. Row filters and column masks apply to the table's owner, and reading through a view does not strip them (verified in one workspace on 2026-08-27). There is no privileged path around an attached policy short of dropping it.

Ordinary tags cannot drive ABAC. A tag-driven policy binds to governed tags; pointing one at a freeform tag fails with an unknown-tag-policy-key error (verified in one workspace on 2026-08-27). The two-key vocabulary in pattern 4 has to be governed tags before any of it enforces.

A type mismatch in a filter fails open. With spark.sql.ansi.enabled = false, a row filter declared on INT against a STRING column returned every row instead of filtering (verified in one workspace on 2026-08-27). Type the filter's parameter to the column, and test with a reader who should see nothing.

Binding overrides grants. A catalog not bound to a workspace is denied there even to a principal holding explicit SELECT (verified in one workspace on 2026-08-27). Binding is the outer gate; a grant inside an unbound catalog entitles nobody.

Audit latency is minutes, not seconds. Rows appeared in system.access.audit roughly ten minutes after the action (verified in one workspace on 2026-08-27). Fine for the compliance queries in pattern 5; not a real-time alarm.

SAP ECC / S/4HANA — entitlement rides the company code, not the client

The landed vendor master carries what SAP guarded with authorization objects, and none of that guarding came with it. What is left is a wide, readable table of every supplier the company has, tax numbers included.

SAP — scope rows by

MANDT pinned in silver is hygiene, not entitlement — it stops two clients summing into one number, which is a correctness problem. Business entitlement rides company code or plant instead: bind the site filter to the plant column on MSEG and a regional analyst sees regional movements.

SAP — mask these

The tax numbers on LFA1 and KNA1STCD1 and STCD2 — and the standard and moving-average prices on MBEW (STPRS, VERPR). Mask product cost to a costing group and leave the quantities open: operations keeps its analysis, finance keeps its numbers.

SAP — the wrinkle

Don't spend a meeting debating whether to mask tax numbers in bronze. Bronze is closed to humans entirely (pattern 2), which is the simpler answer and the one that survives the next table you land.

Landing in Databricks: raw + MERGETable-level CDC & extraction tools

SAP EWM — warehouse execution data is people data

Warehouse execution data is people data. Every confirmed task carries who did it and when, which makes it the most useful productivity dataset in the library and the most sensitive one.

EWM — scope rows by

LGNUM. A warehouse is often a distinct operational — sometimes legal — context, a 3PL-run site being the clearest case, and LGNUM is in every join anyway, so the filter costs nothing to bind.

EWM — mask these

PROCESSOR and CONFIRMED_BY on /SCWM/WHO— the operator who worked the warehouse order and the user who confirmed it. Per-worker productivity is exactly the data that employee-representation agreements restrict in European operations; the practice's judgment, not legal advice, is to mask both or drop them before gold and report at the shift or area grain. RSRC on the same table is in the same protection class: it resolves against the resource master /SCWM/RSRC, and a resource is a named operator as often as it is a device.

EWM — the wrinkle

The slash rename means grants, tags, and masks attach to names that don't look like the source — /SCWM/… lands as scwm_…. Keep the rename map one-to-one, or a policy you believe is attached is attached to something else (namespaced names in a lakehouse).

Namespaced names in a lakehouseRaw table CDC and the open-task problem

JD Edwards — one table mixes customers, suppliers, and employees

JD Edwards is the one reference where a single table mixes constituencies: the address book holds customers, suppliers and employees in one keyspace, discriminated only by a search type code.

JDE — scope rows by

Company keys, plus the business unit (MCU) — right-justified, so trim both sides before the comparison is even valid (business unit padding).

JDE — mask these

The tax id ABTAX on F0101; the supplier bank account RMCBNK on F0413; and the unit cost COUNCS on F4105.

JDE — the wrinkle

Because address-book rows with search type Eare employees, a broad “supply chain master data” grant on F0101 is an HR-data grant too. Filter by search type in silver, or mask, before you grant it widely.

Business unit padding (MCU)Poor-man's CDC: audit columns

Dynamics 365 F&O — the cleanest multi-company entitlement in the library

Dynamics 365 F&O gives you the cleanest multi-company entitlement in the library, because the column the data quality checklist tells you to pin is the same column the row filter wants to bind to.

D365 — scope rows by

DATAAREAID, bound to per-entity groups — company_usmf, company_demf — so adding a legal entity is adding a group, not editing a policy (DataAreaId).

D365 — mask these

BankAccount on VendTable, and the cost amounts on InventTrans CostAmountPosted and CostAmountPhysical.

D365 — the wrinkle

Cost rides the movement table itself rather than sitting in a separate finance table. So a column mask — not a table grant — is what lets an operations analyst work the quantities on InventTrans while the cost columns stay finance-only.

DataAreaId (company partitioning)Incremental & soft deletes

Infor M3 — costing lives at the facility, and the column names lie

M3 puts costing on the item/facility record, which means the scoping column and the sensitive column sit on the same row — one filter and one mask, on one table, no join. The complication is naming: M3 columns are not named what you think they are.

M3 — scope rows by

CONO, the numeric company key, in every join and every filter (CONO).

M3 — mask these

The approved cost M9APPR on MITFAC— the price the facility values the item's inventory at — and the commercial terms on OCUSMA, where the payment terms and the delivery terms and method a customer negotiated sit together.

M3 — the wrinkle

Column prefixes. The same field wears a different two-character prefix per table, so a classification sweep that greps one literal column name misses every alias of it. Classify from the reference's field alias, not the literal name (column prefixes & field aliases).

Column prefixes & field aliasesCONO (company partitioning)

Oracle EBS R12 — the row filter replaces a security layer, not enhances one

Oracle EBS is the reference where the row filter is not an enhancement. MOAC — the operating-unit security the application enforces at runtime — never applies to an extract, so the filter you write is the replacement for a security layer that used to exist.

EBS — scope rows by

ORG_ID, the operating unit, on the _ALL transaction tables; ORGANIZATION_ID, the inventory organization, on the inventory ones. They are two different organizations and picking the wrong one returns rows either way (ORG_ID vs ORGANIZATION_ID, MOAC striping in practice).

EBS — mask these

ITEM_COST and its element buckets on CST_ITEM_COSTS — the unit cost and the material, resource, and overhead splits that reconstruct it.

EBS — the wrinkle

AP_SUPPLIERS is global — there is no org column on it at all — so supplier master data cannot be scoped by operating unit. Entitlement has to ride the transaction tables and the supplier sites that carry the stripe.

ORG_ID vs ORGANIZATION_ID_ALL tables: MOAC striping in practice

Oracle Fusion Cloud SCM — same column name, new meaning

Fusion carries the EBS column name forward with a new meaning, and it carries one more governance risk that no other reference has: the column names you attach policies to are not the ones the source publishes.

Fusion — scope rows by

ORG_ID — which here means business unit, not the EBS operating unit — on DOO_LINES_ALL and its siblings (_ALL now means business unit).

Fusion — mask these

UNIT_SELLING_PRICE on DOO_LINES_ALL — what the buyer actually paid, and the most contract-sensitive figure in the order-to-cash set — and LIST_PRICE_PER_UNIT on EGP_SYSTEM_ITEMS_B.

Fusion — the wrinkle

Masks attach to your landed column names, and BICC headers are PVO attribute names rather than table columns. A drifted rename mapping can silently move sensitive data out from under the mask that was protecting it — which is one more reason the mapping check runs before anything else (mapping headers into bronze/silver).

Mapping PVO headers into bronze/silver_ALL now means business unit

NetSuite — a supply-chain grant on the spine is a finance grant

One spine holds every document NetSuite issues — including the financial ones. That single design fact is the whole governance story here.

NetSuite — scope rows by

Subsidiary, under OneWorld — and then by document type. Because transactioncarries journal entries, vendor payments, checks and deposits beside sales orders and fulfillments (this library's record map says which document lives where), a supply-chain grant on the landed spine is a finance grant unless a row filter on the type column says otherwise (one table family holds every document).

NetSuite — mask these

The costing outputs on inventoryitemlocations averagecostmli and cost.

NetSuite — the wrinkle

Delete blindness is a governance problem, not only a quality one: a row deleted in NetSuite can outlive its deletion in bronze indefinitely. Governing retention means running the delete pass — how NetSuite signals a removed row — on a schedule, not just at build time.

One table family holds every documentOneWorld: subsidiary scoping

What the AI layer inherits

A Genie space is attached to a SQL warehouse its readers need access to, and data access is evaluated as the end user's own Unity Catalog identity — the people asking questions need SELECT on the underlying objects in their own right.

Which means a Genie room never shows a user a row or a column that the grants, filters, and masks of patterns 1 through 4 wouldn't have shown them anyway. The governed layer is the AI security model. There is no second thing to build, and no AI-specific permission surface to reason about.

The inverse is the part worth acting on. An over-broad grant that one analyst might never have noticed — a schema-level SELECT that quietly included the supplier bank accounts — becomes reachable by anyone who can type a question in plain English. AI raises the price of a sloppy grant. It does not raise the price of a good one.

Where the semantic layer picks up from here — the metric definitions, the synonyms, and the certified queries a Genie room or a Cortex Analyst model reads — is the semantic models guide.

The eight references side by side

Nothing new here — every cell restates a section above. Read the second and third columns together: the scoping column decides which rows a reader is entitled to, and the masked columns decide what is left visible on the rows they do get.

ReferenceScope rows byMask theseThe governance wrinkle
SAP ECC / S/4HANACompany code or plant for entitlement — MANDT is correctness, not access controlSTCD1 / STCD2 tax numbers on LFA1 and KNA1; STPRS and VERPR on MBEWBronze is closed to humans, so masking a bronze copy of the vendor master is a debate not worth having
SAP EWMLGNUM — often a distinct operational or legal context, and already in every joinPROCESSOR and CONFIRMED_BY on /SCWM/WHO, plus the RSRC id that resolves to the resource masterNamespaced tables land renamed, so policies attach to names that don't look like the source
JD Edwards EnterpriseOneCompany keys plus a trimmed business unit (MCU)ABTAX on F0101, RMCBNK on F0413, COUNCS on F4105Search type E rows are employees — a broad address-book grant is an HR-data grant
Dynamics 365 F&ODATAAREAID, bound to one group per legal entityBankAccount on VendTable; CostAmountPosted and CostAmountPhysical on InventTransCost rides the movement table, so only a column mask separates operations from finance
Infor M3CONO in every join and every filterM9APPR on MITFAC; the commercial terms on OCUSMATwo-char column prefixes mean a name-based classification sweep misses the aliases
Oracle EBS R12ORG_ID on _ALL transaction tables, ORGANIZATION_ID on inventory tablesITEM_COST and its element buckets on CST_ITEM_COSTSAP_SUPPLIERS is global with no org column, so supplier master data can't be scoped by operating unit
Oracle Fusion Cloud SCMORG_ID, which here means business unitUNIT_SELLING_PRICE on DOO_LINES_ALL; LIST_PRICE_PER_UNIT on EGP_SYSTEM_ITEMS_BPolicies attach to landed names, and BICC headers are PVO attributes — a drifted mapping unmasks silently
NetSuiteSubsidiary under OneWorld, then document type on the transaction spineaveragecostmli and cost on inventoryitemlocationsDeleted rows outlive their deletion in bronze, so retention needs the scheduled delete pass

Every column of this table is a decision the ERP already made for you, and the lakehouse has to re-enforce.

The standing rules

Six rules survive every ERP on this page, and they are the whole page in six lines.

Grant to groups, never to people — and own with groups too. Both break on the same day otherwise.

Humans read gold; pipelines write bronze. Bronze is closed, not masked.

The row-filter column is the ERP's org column. Not always the partition column you pinned for correctness — SAP and NetSuite are the proof.

Mask by exception, with a named audience. Never build a parallel “secure copy” you have to keep in sync.

There is no DENY. Never grant broad intending to claw back.

Lineage and audit are queries. And the AI layer inherits exactly what you governed — no more, no less.

What this page deliberately doesn't cover: each ERP's extraction mechanics, which live in that reference's own extraction guide; the checks that make the numbers right, which are the data quality checklist; delete handling, which is deletes & change tracking; and the storage plumbing underneath all of it — external locations, storage credentials, Delta Sharing recipients. One more omission worth stating: the same layering exists on Snowflake, as role-based grants, row access policies and masking policies. This page is written for Unity Catalog, and the one Snowflake translation this library maintains is in the semantic models guide.

Maintained by Summit Analytics, a supply chain analytics practice. The tools and references are free — the consulting is selective.

Part of the Summit Analytics reference library.

Work with the practice

Not affiliated with or endorsed by Databricks, SAP, Oracle, Microsoft, or Infor. Product names are trademarks of their respective owners.