Policy
Policy answers one question, before anything external happens:
May this actor perform this proposed operation on these resources?
It is trusted configuration. The application writes it; the agent proposes work
against it and can neither choose it, weaken it, nor name itself as someone
else.
policy = gantry.Policy(
name="data-agents",
rules=[
gantry.allow.query(sources=["analytics.*"]),
gantry.allow.materialize(sources=["raw.*"], destinations=["agent_scratch.*"]),
gantry.deny.materialize(destinations=["prod.*"]),
],
)
db = gantry.sql.connect("postgres", url=DATABASE_URL, policy=policy)
with gantry.actor.context(actor=gantry.actor.actor("agent", "etl-agent"), environment="prod"):
run = await db.query(schemas=["analytics"])(sql)
run.admission.policy # "data-agents"
run.admission.policy_hash # "sha256:…"
run.admission.matched_rules # ("allow-query-0",)
Policy decides before the engine is asked
A refused proposal is never submitted. The run exists — it is created before
admission — and carries the decision, with no execution attached to it.
Defining a policy
gantry.Policy
dataclass
A named set of rules, and the hash of exactly those rules.
The version is derived from the rules rather than declared beside them, so
a run that records sha256:… records the configuration that actually
decided it. Two policies that differ anywhere hash differently; the same
rules written in the same order hash the same in any process.
Source code in gantry/policy/model.py
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231 | @dataclass(frozen=True, slots=True)
class Policy:
"""A named set of rules, and the hash of exactly those rules.
The version is derived from the rules rather than declared beside them, so
a run that records `sha256:…` records the configuration that actually
decided it. Two policies that differ anywhere hash differently; the same
rules written in the same order hash the same in any process.
"""
name: str
rules: tuple[PolicyRule, ...] = ()
def __init__(self, name: str, rules: Sequence[PolicyRule] = ()) -> None:
if not isinstance(name, str) or not name.strip():
raise PolicyConfigurationError("policy name must be a non-empty string")
if isinstance(rules, PolicyRule):
raise PolicyConfigurationError("rules must be a list of rules, not one rule")
materialized = tuple(rules)
for rule in materialized:
if not isinstance(rule, PolicyRule):
raise PolicyConfigurationError(f"rules must be PolicyRule objects, got {rule!r}")
object.__setattr__(self, "name", name.strip())
object.__setattr__(self, "rules", tuple(_named(materialized)))
@property
def version(self) -> str:
"""`sha256:…` over the canonical form of the name and rules."""
canonical = json.dumps(
{"name": self.name, "rules": [rule.as_dict() for rule in self.rules]},
sort_keys=True,
separators=(",", ":"),
)
return f"sha256:{sha256(canonical.encode()).hexdigest()}"
def as_dict(self) -> dict[str, object]:
return {
"name": self.name,
"version": self.version,
"rules": [rule.as_dict() for rule in self.rules],
}
|
version
property
sha256:… over the canonical form of the name and rules.
gantry.PolicyRule
dataclass
One rule. Every dimension left as None is unconstrained, except one.
destinations is the exception, and the asymmetry is deliberate: an allow
rule that does not name destinations authorizes no writes at all. Reading
the wrong table is a leak; writing the wrong table destroys something, so
write authority is never granted by omission. deny rules keep the plain
reading — a dimension left out simply does not narrow the rule.
constraints are ceilings the request must already be under. A rule cannot
raise a limit the operation configured, so trusted constraints only ever
compose toward less authority.
require_confirmation does not change the answer — the rule still allows —
it asks the host to tell the user before the work happens. Authority and
"should someone be told" are separate questions, and collapsing them would
turn every sensitive operation into a refusal.
Source code in gantry/policy/model.py
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148 | @dataclass(frozen=True, slots=True)
class PolicyRule:
"""One rule. Every dimension left as `None` is unconstrained, except one.
`destinations` is the exception, and the asymmetry is deliberate: an allow
rule that does not name destinations authorizes no writes at all. Reading
the wrong table is a leak; writing the wrong table destroys something, so
write authority is never granted by omission. `deny` rules keep the plain
reading — a dimension left out simply does not narrow the rule.
`constraints` are ceilings the request must already be under. A rule cannot
raise a limit the operation configured, so trusted constraints only ever
compose toward less authority.
`require_confirmation` does not change the answer — the rule still allows —
it asks the host to tell the user before the work happens. Authority and
"should someone be told" are separate questions, and collapsing them would
turn every sensitive operation into a refusal.
"""
effect: Effect
name: str | None = None
require_confirmation: bool = False
confirmation_code: ConfirmationReasonCode | str | None = None
confirmation_message: str | None = None
actors: tuple[str, ...] | None = None
operations: tuple[OperationKind, ...] | None = None
engines: tuple[str, ...] | None = None
sources: tuple[str, ...] | None = None
destinations: tuple[str, ...] | None = None
environments: tuple[str, ...] | None = None
constraints: Mapping[str, float] = field(default_factory=dict)
def __post_init__(self) -> None:
object.__setattr__(self, "effect", _effect(self.effect))
if self.name is not None and not self.name.strip():
raise PolicyConfigurationError("rule name must not be empty when given")
object.__setattr__(self, "actors", _names(self.actors, "actors"))
object.__setattr__(self, "engines", _names(self.engines, "engines"))
object.__setattr__(self, "environments", _names(self.environments, "environments"))
object.__setattr__(self, "sources", _patterns(self.sources, "sources"))
object.__setattr__(self, "destinations", _patterns(self.destinations, "destinations"))
object.__setattr__(self, "operations", _operations(self.operations))
object.__setattr__(self, "constraints", _constraints(self.constraints))
self._check_confirmation()
def _check_confirmation(self) -> None:
"""A confirmation prompt nobody will ever see is a mistake, not a preference."""
if self.confirmation_code is not None:
try:
object.__setattr__(
self, "confirmation_code", ConfirmationReasonCode(self.confirmation_code)
)
except ValueError as error:
known = ", ".join(code.value for code in ConfirmationReasonCode)
raise PolicyConfigurationError(
f"unknown confirmation code: {self.confirmation_code!r}; v0 has {known}"
) from error
if self.confirmation_message is not None and not self.confirmation_message.strip():
raise PolicyConfigurationError("confirmation message must not be empty when given")
if self.require_confirmation and self.effect is Effect.DENY:
raise PolicyConfigurationError(
"a deny rule cannot require confirmation: nothing it matches will run"
)
if not self.require_confirmation and (
self.confirmation_code is not None or self.confirmation_message is not None
):
raise PolicyConfigurationError(
"confirmation_code and confirmation_message need require_confirmation=True, "
"or nothing will ever show them"
)
def as_dict(self) -> dict[str, object]:
payload: dict[str, object] = {"effect": self.effect.value}
if self.name is not None:
payload["name"] = self.name
if self.require_confirmation:
payload["require_confirmation"] = True
if self.confirmation_code is not None:
payload["confirmation_code"] = str(self.confirmation_code)
if self.confirmation_message is not None:
payload["confirmation_message"] = self.confirmation_message
for key in ("actors", "engines", "sources", "destinations", "environments"):
value = getattr(self, key)
if value is not None:
payload[key] = list(value)
if self.operations is not None:
payload["operations"] = [operation.value for operation in self.operations]
if self.constraints:
payload["constraints"] = dict(sorted(self.constraints.items()))
return payload
|
Allow rules
Rules that grant authority. Omitting sources allows any source to be read;
omitting destinations grants no write authority at all, so the writing
operations require them.
gantry.policy.allow.query
query(
*,
name: str | None = None,
require_confirmation: bool = False,
confirmation_code: ConfirmationReasonCode
| str
| None = None,
confirmation_message: str | None = None,
actors: Sequence[str] | None = None,
engines: Sequence[str] | None = None,
sources: Sequence[str] | None = None,
destinations: Sequence[str] | None = None,
environments: Sequence[str] | None = None,
constraints: Mapping[str, float] | None = None,
) -> PolicyRule
Allow reading. Omitting sources allows any source to be read.
destinations is for the one case where a query writes — a pipeline ending
in $out, an INSERT submitted through a query operation. Omitting it
grants no write authority, so a read policy stays a read policy.
Source code in gantry/policy/allow.py
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54 | def query(
*,
name: str | None = None,
require_confirmation: bool = False,
confirmation_code: ConfirmationReasonCode | str | None = None,
confirmation_message: str | None = None,
actors: Sequence[str] | None = None,
engines: Sequence[str] | None = None,
sources: Sequence[str] | None = None,
destinations: Sequence[str] | None = None,
environments: Sequence[str] | None = None,
constraints: Mapping[str, float] | None = None,
) -> PolicyRule:
"""Allow reading. Omitting `sources` allows any source to be read.
`destinations` is for the one case where a query writes — a pipeline ending
in `$out`, an `INSERT` submitted through a query operation. Omitting it
grants no write authority, so a read policy stays a read policy.
"""
return _allow(
OperationKind.QUERY,
name=name,
require_confirmation=require_confirmation,
confirmation_code=confirmation_code,
confirmation_message=confirmation_message,
actors=actors,
engines=engines,
sources=sources,
destinations=destinations,
environments=environments,
constraints=constraints,
)
|
gantry.policy.allow.materialize
materialize(
*,
destinations: Sequence[str],
name: str | None = None,
require_confirmation: bool = False,
confirmation_code: ConfirmationReasonCode
| str
| None = None,
confirmation_message: str | None = None,
actors: Sequence[str] | None = None,
engines: Sequence[str] | None = None,
sources: Sequence[str] | None = None,
environments: Sequence[str] | None = None,
constraints: Mapping[str, float] | None = None,
) -> PolicyRule
Allow a materialization that lands in destinations.
Source code in gantry/policy/allow.py
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83 | def materialize(
*,
destinations: Sequence[str],
name: str | None = None,
require_confirmation: bool = False,
confirmation_code: ConfirmationReasonCode | str | None = None,
confirmation_message: str | None = None,
actors: Sequence[str] | None = None,
engines: Sequence[str] | None = None,
sources: Sequence[str] | None = None,
environments: Sequence[str] | None = None,
constraints: Mapping[str, float] | None = None,
) -> PolicyRule:
"""Allow a materialization that lands in `destinations`."""
return _allow(
OperationKind.MATERIALIZE,
name=name,
require_confirmation=require_confirmation,
confirmation_code=confirmation_code,
confirmation_message=confirmation_message,
actors=actors,
engines=engines,
sources=sources,
destinations=destinations,
environments=environments,
constraints=constraints,
)
|
gantry.policy.allow.batch
batch(
*,
destinations: Sequence[str],
name: str | None = None,
require_confirmation: bool = False,
confirmation_code: ConfirmationReasonCode
| str
| None = None,
confirmation_message: str | None = None,
actors: Sequence[str] | None = None,
engines: Sequence[str] | None = None,
sources: Sequence[str] | None = None,
environments: Sequence[str] | None = None,
constraints: Mapping[str, float] | None = None,
) -> PolicyRule
Allow a batch job that writes into destinations.
Source code in gantry/policy/allow.py
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112 | def batch(
*,
destinations: Sequence[str],
name: str | None = None,
require_confirmation: bool = False,
confirmation_code: ConfirmationReasonCode | str | None = None,
confirmation_message: str | None = None,
actors: Sequence[str] | None = None,
engines: Sequence[str] | None = None,
sources: Sequence[str] | None = None,
environments: Sequence[str] | None = None,
constraints: Mapping[str, float] | None = None,
) -> PolicyRule:
"""Allow a batch job that writes into `destinations`."""
return _allow(
OperationKind.BATCH,
name=name,
require_confirmation=require_confirmation,
confirmation_code=confirmation_code,
confirmation_message=confirmation_message,
actors=actors,
engines=engines,
sources=sources,
destinations=destinations,
environments=environments,
constraints=constraints,
)
|
gantry.policy.allow.stream
stream(
*,
destinations: Sequence[str],
name: str | None = None,
require_confirmation: bool = False,
confirmation_code: ConfirmationReasonCode
| str
| None = None,
confirmation_message: str | None = None,
actors: Sequence[str] | None = None,
engines: Sequence[str] | None = None,
sources: Sequence[str] | None = None,
environments: Sequence[str] | None = None,
constraints: Mapping[str, float] | None = None,
) -> PolicyRule
Allow submitting a streaming job that writes into destinations.
Source code in gantry/policy/allow.py
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141 | def stream(
*,
destinations: Sequence[str],
name: str | None = None,
require_confirmation: bool = False,
confirmation_code: ConfirmationReasonCode | str | None = None,
confirmation_message: str | None = None,
actors: Sequence[str] | None = None,
engines: Sequence[str] | None = None,
sources: Sequence[str] | None = None,
environments: Sequence[str] | None = None,
constraints: Mapping[str, float] | None = None,
) -> PolicyRule:
"""Allow submitting a streaming job that writes into `destinations`."""
return _allow(
OperationKind.STREAM,
name=name,
require_confirmation=require_confirmation,
confirmation_code=confirmation_code,
confirmation_message=confirmation_message,
actors=actors,
engines=engines,
sources=sources,
destinations=destinations,
environments=environments,
constraints=constraints,
)
|
Deny rules
A matching deny wins over every allow.
gantry.policy.deny.query
query(
*,
name: str | None = None,
actors: Sequence[str] | None = None,
engines: Sequence[str] | None = None,
sources: Sequence[str] | None = None,
destinations: Sequence[str] | None = None,
environments: Sequence[str] | None = None,
constraints: Mapping[str, float] | None = None,
) -> PolicyRule
Deny queries matching this rule.
Source code in gantry/policy/deny.py
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38 | def query(
*,
name: str | None = None,
actors: Sequence[str] | None = None,
engines: Sequence[str] | None = None,
sources: Sequence[str] | None = None,
destinations: Sequence[str] | None = None,
environments: Sequence[str] | None = None,
constraints: Mapping[str, float] | None = None,
) -> PolicyRule:
"""Deny queries matching this rule."""
return _deny(
OperationKind.QUERY,
name=name,
actors=actors,
engines=engines,
sources=sources,
destinations=destinations,
environments=environments,
constraints=constraints,
)
|
gantry.policy.deny.materialize
materialize(
*,
destinations: Sequence[str] | None = None,
name: str | None = None,
actors: Sequence[str] | None = None,
engines: Sequence[str] | None = None,
sources: Sequence[str] | None = None,
environments: Sequence[str] | None = None,
constraints: Mapping[str, float] | None = None,
) -> PolicyRule
Deny materializations matching this rule.
Source code in gantry/policy/deny.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61 | def materialize(
*,
destinations: Sequence[str] | None = None,
name: str | None = None,
actors: Sequence[str] | None = None,
engines: Sequence[str] | None = None,
sources: Sequence[str] | None = None,
environments: Sequence[str] | None = None,
constraints: Mapping[str, float] | None = None,
) -> PolicyRule:
"""Deny materializations matching this rule."""
return _deny(
OperationKind.MATERIALIZE,
name=name,
actors=actors,
engines=engines,
sources=sources,
destinations=destinations,
environments=environments,
constraints=constraints,
)
|
gantry.policy.deny.batch
batch(
*,
destinations: Sequence[str] | None = None,
name: str | None = None,
actors: Sequence[str] | None = None,
engines: Sequence[str] | None = None,
sources: Sequence[str] | None = None,
environments: Sequence[str] | None = None,
constraints: Mapping[str, float] | None = None,
) -> PolicyRule
Deny batch jobs matching this rule.
Source code in gantry/policy/deny.py
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84 | def batch(
*,
destinations: Sequence[str] | None = None,
name: str | None = None,
actors: Sequence[str] | None = None,
engines: Sequence[str] | None = None,
sources: Sequence[str] | None = None,
environments: Sequence[str] | None = None,
constraints: Mapping[str, float] | None = None,
) -> PolicyRule:
"""Deny batch jobs matching this rule."""
return _deny(
OperationKind.BATCH,
name=name,
actors=actors,
engines=engines,
sources=sources,
destinations=destinations,
environments=environments,
constraints=constraints,
)
|
gantry.policy.deny.stream
stream(
*,
destinations: Sequence[str] | None = None,
name: str | None = None,
actors: Sequence[str] | None = None,
engines: Sequence[str] | None = None,
sources: Sequence[str] | None = None,
environments: Sequence[str] | None = None,
constraints: Mapping[str, float] | None = None,
) -> PolicyRule
Deny streaming submissions matching this rule.
Source code in gantry/policy/deny.py
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107 | def stream(
*,
destinations: Sequence[str] | None = None,
name: str | None = None,
actors: Sequence[str] | None = None,
engines: Sequence[str] | None = None,
sources: Sequence[str] | None = None,
environments: Sequence[str] | None = None,
constraints: Mapping[str, float] | None = None,
) -> PolicyRule:
"""Deny streaming submissions matching this rule."""
return _deny(
OperationKind.STREAM,
name=name,
actors=actors,
engines=engines,
sources=sources,
destinations=destinations,
environments=environments,
constraints=constraints,
)
|
gantry.policy.deny.anything
anything(
*,
name: str | None = None,
actors: Sequence[str] | None = None,
engines: Sequence[str] | None = None,
sources: Sequence[str] | None = None,
destinations: Sequence[str] | None = None,
environments: Sequence[str] | None = None,
constraints: Mapping[str, float] | None = None,
) -> PolicyRule
Deny across every operation — gantry.deny.anything(environments=["prod"]).
Source code in gantry/policy/deny.py
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130 | def anything(
*,
name: str | None = None,
actors: Sequence[str] | None = None,
engines: Sequence[str] | None = None,
sources: Sequence[str] | None = None,
destinations: Sequence[str] | None = None,
environments: Sequence[str] | None = None,
constraints: Mapping[str, float] | None = None,
) -> PolicyRule:
"""Deny across every operation — `gantry.deny.anything(environments=["prod"])`."""
return _deny(
None,
name=name,
actors=actors,
engines=engines,
sources=sources,
destinations=destinations,
environments=environments,
constraints=constraints,
)
|
What policy decides on
gantry.PolicyRequest
dataclass
One proposed operation, normalized away from any engine's syntax.
Built by provider inspection, never by the agent: inputs and outputs
are what Gantry determined the proposal touches, not what it claimed to
touch. constraints are the bounds the operation was configured with, so a
rule can require that a request already sits under a ceiling.
unresolved is how inspection says it could not tell. A request carrying
any is denied — an effect nobody can name is not one anybody authorized.
Source code in gantry/policy/request.py
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57 | @dataclass(frozen=True, slots=True)
class PolicyRequest:
"""One proposed operation, normalized away from any engine's syntax.
Built by provider inspection, never by the agent: `inputs` and `outputs`
are what Gantry determined the proposal touches, not what it claimed to
touch. `constraints` are the bounds the operation was configured with, so a
rule can require that a request already sits under a ceiling.
`unresolved` is how inspection says it could not tell. A request carrying
any is denied — an effect nobody can name is not one anybody authorized.
"""
actor: ActorRef
operation: OperationKind
engine: str
inputs: tuple[ResourceRef, ...] = ()
outputs: tuple[ResourceRef, ...] = ()
environment: str | None = None
constraints: Mapping[str, float] = field(default_factory=dict)
unresolved: tuple[str, ...] = ()
def __post_init__(self) -> None:
object.__setattr__(self, "operation", OperationKind(self.operation))
object.__setattr__(self, "inputs", tuple(self.inputs))
object.__setattr__(self, "outputs", tuple(self.outputs))
object.__setattr__(self, "unresolved", tuple(self.unresolved))
environment = self.environment
if environment is not None:
object.__setattr__(self, "environment", environment.strip().lower() or None)
def as_dict(self) -> dict[str, object]:
payload: dict[str, object] = {
"actor": self.actor.label,
"operation": self.operation.value,
"engine": self.engine,
"inputs": [ref.resource for ref in self.inputs],
"outputs": [ref.resource for ref in self.outputs],
"environment": self.environment,
}
if self.constraints:
payload["constraints"] = dict(sorted(self.constraints.items()))
if self.unresolved:
payload["unresolved"] = list(self.unresolved)
return payload
|
What it returns
gantry.PolicyDecision
dataclass
Whether the proposal is authorized, under which policy, and why.
Carried onto the run whether it allowed or refused. An allow that records
nothing is indistinguishable later from an operation nobody checked.
confirmation is a second, independent answer: the operation is allowed,
and the host should tell the user before it happens. A denial never carries
one — there is nothing to confirm about work that will not run.
Source code in gantry/policy/decision.py
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98 | @dataclass(frozen=True, slots=True)
class PolicyDecision:
"""Whether the proposal is authorized, under which policy, and why.
Carried onto the run whether it allowed or refused. An allow that records
nothing is indistinguishable later from an operation nobody checked.
`confirmation` is a second, independent answer: the operation is allowed,
and the host should tell the user before it happens. A denial never carries
one — there is nothing to confirm about work that will not run.
"""
allowed: bool
policy: str
policy_version: str
matched_rules: tuple[str, ...] = ()
reasons: tuple[PolicyReason, ...] = ()
confirmation: ConfirmationRequirement = NOT_REQUIRED
evaluated_at: datetime = field(default_factory=lambda: datetime.now(UTC))
@property
def codes(self) -> tuple[str, ...]:
return tuple(dict.fromkeys(reason.code.value for reason in self.reasons))
def messages(self) -> tuple[str, ...]:
return tuple(dict.fromkeys(str(reason) for reason in self.reasons))
def as_dict(self) -> dict[str, object]:
return {
"allowed": self.allowed,
"policy": self.policy,
"policy_version": self.policy_version,
"matched_rules": list(self.matched_rules),
"reasons": [reason.as_dict() for reason in self.reasons],
"confirmation": self.confirmation.as_dict(),
"evaluated_at": self.evaluated_at.isoformat(),
}
|
gantry.PolicyReason
dataclass
One reason, tied to the resource and rule it came from.
Source code in gantry/policy/decision.py
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59 | @dataclass(frozen=True, slots=True)
class PolicyReason:
"""One reason, tied to the resource and rule it came from."""
code: PolicyReasonCode
resource: str | None = None
rule: str | None = None
message: str | None = None
def __str__(self) -> str:
if self.message:
return self.message
parts = [self.code.value]
if self.resource:
parts.append(self.resource)
if self.rule:
parts.append(f"rule {self.rule}")
return ": ".join(parts)
def as_dict(self) -> dict[str, object]:
return {
"code": self.code.value,
"resource": self.resource,
"rule": self.rule,
"message": str(self),
}
|
gantry.PolicyReasonCode
Bases: StrEnum
Why a decision came out the way it did, in machine-readable form.
A denial that says only "permission denied" cannot be acted on by the agent
that hit it, triaged by the operator who reads it, or counted by anyone
asking which rule keeps firing.
Source code in gantry/policy/decision.py
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 | class PolicyReasonCode(StrEnum):
"""Why a decision came out the way it did, in machine-readable form.
A denial that says only "permission denied" cannot be acted on by the agent
that hit it, triaged by the operator who reads it, or counted by anyone
asking which rule keeps firing.
"""
NO_MATCHING_ALLOW = "NO_MATCHING_ALLOW"
EXPLICIT_DENY = "EXPLICIT_DENY"
ACTOR_DENIED = "ACTOR_DENIED"
OPERATION_DENIED = "OPERATION_DENIED"
SOURCE_DENIED = "SOURCE_DENIED"
DESTINATION_DENIED = "DESTINATION_DENIED"
ENVIRONMENT_DENIED = "ENVIRONMENT_DENIED"
CONSTRAINT_EXCEEDED = "CONSTRAINT_EXCEEDED"
RESOURCE_UNRESOLVED = "RESOURCE_UNRESOLVED"
POLICY_INVALID = "POLICY_INVALID"
|
When a policy cannot mean anything
gantry.PolicyConfigurationError
Bases: ValueError
A policy that cannot mean anything, raised where it is written.
A policy is trusted configuration, so the useful time to reject it is at
construction — while the author is looking at it — rather than at admission,
where the agent sees a refusal it cannot act on and the operator sees a
denial that was really a typo.
Source code in gantry/policy/errors.py
| class PolicyConfigurationError(ValueError):
"""A policy that cannot mean anything, raised where it is written.
A policy is trusted configuration, so the useful time to reject it is at
construction — while the author is looking at it — rather than at admission,
where the agent sees a refusal it cannot act on and the operator sees a
denial that was really a typo.
"""
|
Resource patterns
gantry.policy.normalize_pattern
normalize_pattern(pattern: str) -> str
Validate one pattern and fold it to its matching form.
Accepted: events, analytics.*, project.dataset.*, scratch_* and a
bare *. Everything else — a star inside a name, an empty segment, a
non-final star — is a configuration error rather than a pattern that
silently matches nothing.
Source code in gantry/policy/patterns.py
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52 | def normalize_pattern(pattern: str) -> str:
"""Validate one pattern and fold it to its matching form.
Accepted: `events`, `analytics.*`, `project.dataset.*`, `scratch_*` and a
bare `*`. Everything else — a star inside a name, an empty segment, a
non-final star — is a configuration error rather than a pattern that
silently matches nothing.
"""
if not isinstance(pattern, str):
raise PolicyConfigurationError(f"resource pattern must be a string, not {type(pattern)}")
cleaned = pattern.strip()
if not cleaned:
raise PolicyConfigurationError("resource pattern must not be empty")
segments = cleaned.split(".")
for index, segment in enumerate(segments):
if not segment:
raise PolicyConfigurationError(f"resource pattern has an empty segment: {pattern!r}")
last = index == len(segments) - 1
if _WILDCARD not in segment:
continue
if not last:
raise PolicyConfigurationError(f"'*' is only allowed in the last segment: {pattern!r}")
if segment.count(_WILDCARD) > 1 or not segment.endswith(_WILDCARD):
raise PolicyConfigurationError(f"'*' is only allowed at the end of a name: {pattern!r}")
return cleaned.lower()
|
gantry.policy.matches
matches(pattern: str, resource: str) -> bool
Does one normalized pattern cover this resource name?
A whole * segment covers one or more remaining segments, so analytics.*
covers analytics.customers and analytics.public.customers but not
analytics itself — the pattern names things in a namespace, and a
namespace is not one of its own members.
A prefix* segment covers that one segment only: scratch_* matches
scratch_orders and not scratch_orders.v2, because a prefix is a name, not
a namespace.
Source code in gantry/policy/patterns.py
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82 | def matches(pattern: str, resource: str) -> bool:
"""Does one normalized pattern cover this resource name?
A whole `*` segment covers one or more remaining segments, so `analytics.*`
covers `analytics.customers` and `analytics.public.customers` but not
`analytics` itself — the pattern names things *in* a namespace, and a
namespace is not one of its own members.
A `prefix*` segment covers that one segment only: `scratch_*` matches
`scratch_orders` and not `scratch_orders.v2`, because a prefix is a name, not
a namespace.
"""
name = resource.strip().lower()
if not name:
return False
if pattern == _WILDCARD:
return True
expected = pattern.split(".")
actual = name.split(".")
tail = expected[-1]
if tail == _WILDCARD:
prefix = expected[:-1]
return len(actual) > len(prefix) and actual[: len(prefix)] == prefix
if not tail.endswith(_WILDCARD):
return expected == actual
if len(actual) != len(expected) or actual[:-1] != expected[:-1]:
return False
return actual[-1].startswith(tail[:-1])
|