Skip to content

ump_spec.rules.data

module ump-spec

Rules for the data catalogue and connections (POC blueprint DATA-CATALOGUE §2, §4, §6). Pure functions.

  • canonical_rows_fingerprint: the sha256 a connected dataset registers and every fetch compares against (fingerprint_basis: canonical_rows).
  • normalise_query: the identity a clean-room release is recorded under (G-PRIV-03).
  • effective_access: the access policy with the client contract’s defaults filled (retention, G-PRIV-01 floor).
  • check_use: refuses a use the client did not grant (USE_NOT_ALLOWED), transform on data whose copy policy forbids it, and an agent reading rows it may not see (TOOL_SEPARATION, G-NEW-02).
  • access_plain_words: the policy in one sentence for the catalogue.

function

def access_plain_words(
access: AccessPolicy | Mapping[str, Any] | None,
*,
storage: str = 'stored',
) -> str

One sentence for the catalogue, e.g. “No copies kept (read for each run, then dropped); allowed for fit, score and side modelling.”.

Source: packages/ump-spec/src/ump_spec/rules/data.py, line 142

function

def canonical_rows_fingerprint(
columns: Sequence[str],
rows: Sequence[Sequence[Any] | Mapping[str, Any]],
) -> str

sha256 hex of canonical_json({"columns": [...], "rows": [[...], ...]}) over a canonical layout.

The hash depends on the data, not on the order the source happened to return it in:

  • columns are sorted by name and every row’s cells are permuted to match, so an API that serialises its objects with a different key order (or a SQL view whose column order changed) gives the same hash;
  • rows are sorted by their canonical JSON, so a table read without ORDER BY (Postgres promises no order) gives the same hash.

Mapping rows are read by column name (missing keys are null). Any changed, added or removed value, row or column still changes the fingerprint, so restated weeks are seen. Cell values must already be plain JSON values (connectors.base.plain_cell); anything else raises TypeError or ValueError.

Source: packages/ump-spec/src/ump_spec/rules/data.py, line 33

function

def check_use(
access: AccessPolicy | Mapping[str, Any] | None,
use: str,
*,
principal: str | None = None,
storage: str = 'stored',
licence_allows_pooling: bool | None = None,
) -> AccessPolicy

Raise when the read is not allowed; return the effective policy when it is.

  • An agent principal (agent:/token:) never reads rows of a transient_only or aggregates_only dataset: TOOL_SEPARATION (G-NEW-02, hard stop). Agents read catalogue entries.
  • A use missing from allowed_uses: USE_NOT_ALLOWED (403). The client did not grant it, so this is a refusal, not a gate fallback.
  • benchmark also needs the licence to allow pooling when that is known (licence_allows_pooling).
  • transform (reading rows into an analyst’s notebook to prepare a derived dataset) puts a copy of the client’s rows outside the platform, so it is never granted by default. Under allowed and transient_only it needs to be listed in allowed_uses (for transient_only the client is agreeing that the rows sit on an analyst’s laptop for one session, and nothing derived from them may be stored). Under aggregates_only it is always refused, even when listed: rows below the minimum group size never leave the clean room.

Source: packages/ump-spec/src/ump_spec/rules/data.py, line 99

function

def effective_access(
access: AccessPolicy | Mapping[str, Any] | None,
contract: ContractTerms | Mapping[str, Any] | None = None,
*,
storage: str = 'stored',
) -> AccessPolicy

The policy that governs a read. A stored dataset without its own policy is allowed (the client sent the file); a connected one defaults to transient_only. Retention and the minimum segment size fall back to the contract (retention_days), then to DEFAULT_MIN_SEGMENT. The minimum segment size is the stricter (larger) of the policy’s and the contract’s when a contract is known (G-PRIV-01).

Source: packages/ump-spec/src/ump_spec/rules/data.py, line 74

function

def fingerprints_differ(registered: str | None, seen: str | None) -> bool

True when both are known and differ (sha256: prefixes ignored). Unknown is never drift.

Source: packages/ump-spec/src/ump_spec/rules/data.py, line 67

function

def normalise_query(query: Any) -> str

The identity a clean-room release is recorded under for the G-PRIV-03 overlap check: whitespace collapsed and case folded, so "buyers_q3 " and "Buyers_Q3" are the same query.

Source: packages/ump-spec/src/ump_spec/rules/data.py, line 61

NameTypeValueDescription
DEFAULT_MIN_SEGMENT50Floor used when neither the policy nor the contract sets one (ContractTerms default, G-PRIV-01).