Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
cccfeb0
[SPEC] Add FGAC enforcement instructions as part of loadTable
sfc-gh-prsingh Aug 20, 2025
c2900ef
Address review feedbacks
sfc-gh-prsingh Aug 21, 2025
4c567a7
Address review feedbacks
sfc-gh-prsingh Aug 21, 2025
0138cbf
Address review feedback
sfc-gh-prsingh Aug 22, 2025
65b88d3
Address review feedbacks and align to RFC-2119
sfc-gh-prsingh Aug 25, 2025
1e8f75c
Make who the evaluated policy is applicable to more verbose
sfc-gh-prsingh Aug 26, 2025
ac7ad1f
Add projection must be applied after filtering
sfc-gh-prsingh Aug 27, 2025
239ce16
Add restriction for data type match
sfc-gh-prsingh Aug 28, 2025
6cb36e4
[Ongoing Discussion] Add projections and filters are per current sche…
sfc-gh-prsingh Sep 8, 2025
da91571
Address review feedbacks
sfc-gh-prsingh Sep 25, 2025
7f0a6e5
Address review feedback
sfc-gh-prsingh Oct 30, 2025
c74993a
Address review feedback
sfc-gh-prsingh Nov 5, 2025
996709a
Address review feedback
sfc-gh-prsingh Nov 10, 2025
0a13b5a
pass lint
sfc-gh-prsingh Dec 14, 2025
794f52e
Address review feedback from steven
sfc-gh-prsingh Jan 11, 2026
b7e61f6
Address feedbacks per sync 02/03/2026
sfc-gh-prsingh Feb 17, 2026
9cf12e7
Address comment: clarify unlisted columns wording
sfc-gh-prsingh Mar 9, 2026
94ae913
Add discriminator for Action and remove Projection wrapper
sfc-gh-prsingh Mar 16, 2026
a14cdad
Model predefined masks as Actions and replace ApplyTransform with App…
sfc-gh-prsingh Apr 17, 2026
53f3841
Tighten spec precision for catalog and engine implementers
sfc-gh-prsingh Apr 18, 2026
a8d3503
Remove uuid from SHA-256 action applicable types
sfc-gh-prsingh Apr 18, 2026
570573d
Fix timestamp_ns mask-to-default overflow in spec
sfc-gh-prsingh Apr 22, 2026
a0e0d27
Open API: Address FGAC spec review feedback
sfc-gh-prsingh May 2, 2026
47258ec
Open API: Refine FGAC read restrictions spec
sfc-gh-prsingh May 17, 2026
ed944a3
Spec: V1 conditional wording for read-restrictions
sfc-gh-prsingh Jun 9, 2026
2e91958
Spec: Update Python model with V1 read-restrictions wording
sfc-gh-prsingh Jun 9, 2026
3dc5a79
Spec: Lowercase RFC2119 keywords and drop redundant MAYs
sfc-gh-prsingh Jun 14, 2026
a4dcd0b
Spec: Restructure ReadRestrictions per rdblue review
sfc-gh-prsingh Jun 14, 2026
159f47c
Spec: Rewrite ReadRestrictions property descriptions per rdblue review
sfc-gh-prsingh Jun 14, 2026
cfca721
Spec: Restore numbered rule lists in ReadRestrictions properties
sfc-gh-prsingh Jun 14, 2026
e0770a4
Spec: Per-action wording fixes per rdblue review
sfc-gh-prsingh Jun 14, 2026
670348a
Spec: Replace em-dash with colon in Sha256Global description
sfc-gh-prsingh Jun 22, 2026
2f18458
Spec: Regenerate rest-catalog-open-api.py
sfc-gh-prsingh Jun 23, 2026
d918afc
Spec: Address review nits and restore nested-projection prohibition
sfc-gh-prsingh Jun 26, 2026
8c82b79
Spec: Forbid projections on map key field-ids
sfc-gh-prsingh Jun 29, 2026
f5ed94e
Spec: Regenerate rest-catalog-open-api.py with datamodel-code-generat…
sfc-gh-prsingh Jun 29, 2026
422315a
Spec: Use 'Iceberg predicates' instead of 'Iceberg expressions' for N…
sfc-gh-prsingh Jun 29, 2026
bffa084
Spec: Exclude geometry/geography from MaskToFixedValue
sfc-gh-prsingh Jun 29, 2026
98d79a0
Spec: Address review comments on read restrictions wording
sfc-gh-prsingh Jul 1, 2026
d67103b
Spec: Address Steven's comments on field-id presence in read schema
sfc-gh-prsingh Jul 1, 2026
14125c5
Spec: Regenerate rest-catalog-open-api.py
sfc-gh-prsingh Jul 1, 2026
5af00a6
Spec: Address review comments on ReadRestrictions rule 5 and wording
sfc-gh-prsingh Jul 9, 2026
92e76c5
Spec: Regenerate rest-catalog-open-api.py
sfc-gh-prsingh Jul 9, 2026
67aa35a
Spec: Reframe MaskToFixedValue applicable-to as an allowlist
sfc-gh-prsingh Jul 9, 2026
5044d53
Spec: Regenerate rest-catalog-open-api.py
sfc-gh-prsingh Jul 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
184 changes: 184 additions & 0 deletions open-api/rest-catalog-open-api.py
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,154 @@ class StorageCredential(BaseModel):
config: dict[str, str]


class Action(BaseModel):
action: str
field_id: int = Field(
..., alias='field-id', description='Field ID of the column being projected.'
)


class MaskAlphanum(Action):
"""
Redacts the column value using the following rules to transform Unicode code points:
- Digits (U+0030–U+0039, 0-9) are replaced with 'n' - The following punctuation characters are kept as-is:
U+0028 '(' LEFT PARENTHESIS
U+0029 ')' RIGHT PARENTHESIS
U+002C ',' COMMA
U+002E '.' FULL STOP
U+002D '-' HYPHEN-MINUS
U+0040 '@' COMMERCIAL AT
- All other Unicode characters (including letters, whitespace, and any punctuation
not listed above) are replaced with 'x'

For example: "prashant010696@gmail.com" -> "xxxxxxxxnnnnnn@xxxxx.xxx"
NULL input is preserved (NULL -> NULL).
Applicable to: string

"""

action: Literal['mask-alphanum'] | None = None


class MaskToFixedValue(Action):
"""
Replaces the column value with a type-specific fixed value. Readers must use exactly the values listed below to ensure consistency across implementations.
Fixed values by type: - boolean: false - int: 0 - long: 0 - float: 0.0 - double: 0.0 - decimal(p, s): 0 (the unscaled value is 0) - string: "XXXXXXXX" - date: 1970-01-01 - time: 00:00:00 - timestamp: 1970-01-01T00:00:00 - timestamptz: 1970-01-01T00:00:00+00:00 - timestamp_ns: 1970-01-01T00:00:00.000000000 - timestamptz_ns: 1970-01-01T00:00:00.000000000+00:00 - uuid: 00000000-0000-0000-0000-000000000000 - fixed(n): n zero bytes - binary: empty byte sequence - variant: {} - list: empty list [] - map: empty map {} - struct: struct with each field set to its type-specific default (applied recursively)
NULL input is also replaced with the type-specific fixed value; NULL is not preserved.
Applicable to: the types with a fixed value defined above. A catalog server MUST NOT return mask-to-fixed-value for any other type.

"""

action: Literal['mask-to-fixed-value'] | None = None


class ReplaceWithNull(Action):
"""
Replaces the column value with NULL. NULL input is preserved (NULL -> NULL).
Applicable to: all optional types. Applying to a required (non-nullable) column is invalid.

"""

action: Literal['replace-with-null'] | None = None


class ShowFirst4(Action):
"""
Preserves the first 4 Unicode code points of the column value and redacts the remainder using mask-alphanum rules (see MaskAlphanum for the exact character rules). Values with 4 or fewer Unicode code points are returned unchanged.
For example: "prashant010696@gmail.com" -> "prasxxxxnnnnnn@xxxxx.xxx"
NULL input is preserved (NULL -> NULL).
Applicable to: string

"""

action: Literal['show-first-4'] | None = None


class ShowLast4(Action):
"""
Redacts all Unicode code points except the last 4 using mask-alphanum rules (see MaskAlphanum for the exact character rules). Values with 4 or fewer Unicode code points are returned unchanged.
For example: "4111-1111-1111-4444" -> "nnnn-nnnn-nnnn-4444"
NULL input is preserved (NULL -> NULL).
Applicable to: string

"""

action: Literal['show-last-4'] | None = None


class TruncateToYear(Action):
"""
Truncates the column value to year precision, setting month, day, and time components to their minimum values. The output type matches the input type.
For example: 2024-07-15 -> 2024-01-01 For timestamptz and timestamptz_ns, truncation is performed in UTC.
NULL input is preserved (NULL -> NULL).
Applicable to: date, timestamp, timestamptz, timestamp_ns, timestamptz_ns

"""

action: Literal['truncate-to-year'] | None = None


class TruncateToMonth(Action):
"""
Truncates the column value to year and month precision, setting day and time components to their minimum values. The output type matches the input type.
For example: 2024-07-15 -> 2024-07-01 For timestamptz and timestamptz_ns, truncation is performed in UTC.
NULL input is preserved (NULL -> NULL).
Applicable to: date, timestamp, timestamptz, timestamp_ns, timestamptz_ns

"""

action: Literal['truncate-to-month'] | None = None


class Sha256Global(Action):
"""
Applies SHA-256. Deterministic across all queries
and readers: the same input always produces the same output.

Input-to-bytes encoding by type:
- string: UTF-8 encoded bytes
- int: 4 bytes, little-endian
- long: 8 bytes, little-endian
- binary: raw bytes as-is

Output encoding by type:
- string: 64-character lowercase hexadecimal string
- int: first 4 bytes of the digest, read as a little-endian int
- long: first 8 bytes of the digest, read as a little-endian long
- binary: the full 32-byte raw SHA-256 digest

NULL input is preserved (NULL -> NULL).

Applicable to: string, int, long, binary

"""

action: Literal['sha-256-global'] | None = None


class Sha256QueryLocal(Action):
"""
Applies SHA-256 with a per-query random salt, making the output non-deterministic
across queries while remaining consistent within a single query. The definition
of a query is left to the implementation.

The reader must generate a cryptographically random salt of at least 16 bytes for each query.

For each column value, the reader must encode the value to bytes using
sha-256-global's input rules, prepend the per-query salt, and compute
SHA-256 over the result.

Output encoding follows the same rules as sha-256-global.

NULL input is preserved (NULL -> NULL).

Applicable to: string, int, long, binary

"""

action: Literal['sha-256-query-local'] | None = None


class LoadCredentialsResponse(BaseModel):
storage_credentials: list[StorageCredential] = Field(
..., alias='storage-credentials'
Expand Down Expand Up @@ -1526,6 +1674,40 @@ class AddSchemaUpdate(BaseUpdate):
)


class ReadRestrictions(BaseModel):
"""
Read restrictions for a table.
A reader evaluates the row filter against original, untransformed column values, then applies required-column-projections to the surviving rows. Each action must produce a value of the same type as the input column. If a reader that supports read-restrictions cannot apply any returned restriction (a filter expression or an action), it must fail the query and must not silently return raw, partial, or empty results.
If a projection targets a nested-typed field (struct, list, or map), other projections in the same ReadRestrictions must not target any nested field-id (struct subfields, list elements, or map keys/values) at any depth. This avoids ambiguity about which action governs a given leaf value.
A missing or empty ReadRestrictions object (no required-column-projections and no required-row-filter) imposes no restrictions.

"""

required_column_projections: (
list[
MaskAlphanum
| MaskToFixedValue
| ReplaceWithNull
| ShowFirst4
| ShowLast4
| TruncateToYear
| TruncateToMonth
| Sha256Global
| Sha256QueryLocal
]
| None
) = Field(
None,
alias='required-column-projections',
Comment thread
singhpk234 marked this conversation as resolved.
description='A list of columns that require specific actions to be applied when reading. A server must not return an action for a column whose type is not listed in that action\'s "Applicable to" set. If absent or empty, no required actions apply; columns not listed are not subject to any required action.\n1. For each column listed, the reader must apply the specified action before\n returning values for that column.\n\n2. The reader must replace all output references to the column with the result\n of the action, presenting the result under the original field-id. For\n example, if the action for field-id `9` is mask-alphanum, the reader must\n return the masked value as field-id `9` in the query output.\n\n3. A server must not return more than one projection for the same field-id\n in required-column-projections. If a duplicate field-id appears, the reader\n must fail the query.\n\n4. A projection must not target a map\'s key field-id. Applying an action\n to keys can produce duplicate or null keys, which readers silently\n coalesce or reject, causing data loss.\n\n5. A reader must enforce projections on the columns it is actually reading.\n Projections referencing columns that are not being read do not apply.\n',
)
required_row_filter: Expression | None = Field(
None,
alias='required-row-filter',
description='An expression that limits which rows the reader may return.\n1. The expression must evaluate to a boolean (TRUE or FALSE; Iceberg predicates\n never produce NULL). A reader must discard any row for which the filter\n evaluates to FALSE, and no information derived from discarded rows may be\n included in the query result.\n\n2. If this property is absent, null, or always true then no mandatory filtering is required.\n',
)


class LoadTableResult(BaseModel):
"""
Result used when a table is successfully loaded.
Expand Down Expand Up @@ -1580,6 +1762,7 @@ class LoadTableResult(BaseModel):
storage_credentials: list[StorageCredential] | None = Field(
None, alias='storage-credentials'
)
read_restrictions: ReadRestrictions | None = Field(None, alias='read-restrictions')


class ScanTasks(BaseModel):
Expand Down Expand Up @@ -2089,6 +2272,7 @@ class PlanTableScanResult(
TableMetadata.model_rebuild()
ViewMetadata.model_rebuild()
AddSchemaUpdate.model_rebuild()
ReadRestrictions.model_rebuild()
ScanTasks.model_rebuild()
CommitTableRequest.model_rebuild()
CommitViewRequest.model_rebuild()
Expand Down
Loading
Loading