Policy model¶
The policy model is a deterministic, typed evaluator with explicit predicates — not a scripting language and not embedded policy code. A policy states explicit requirements; the evaluator reports, per requirement, whether it is satisfied, violated, or unresolved, then combines those into one outcome.
A policy, concretely¶
apiVersion: xila-guard/v1alpha1
kind: PlacementPolicy
metadata:
name: tenant-dedicated-baseline
spec:
placementScope: node
requirements:
allowedRegions:
- eu-central-1
nodeTenantAssignment: sameTenant
gpu:
timeSlicing: forbidden
mps: forbidden
allowedPartitioning:
- none
approvedDriverVersions:
- "fixture-driver-1"
attestation: notRequired
evidence:
maxAgeSeconds: 60
maxClockSkewSeconds: 5
fixture-driver-1 is synthetic
It is a test value, not a recommended driver. A real installation needs an operator-maintained approved set, and a successful check means version conformity, not absence of vulnerabilities.
Every enum, empty list, omitted optional field, and default has documented
meaning. An empty allowlist matches nothing. Missing required fields make the
policy invalid (a validation error, not a decision). An explicit notRequired
means the predicate is simply not requested — it is not evidence that the
property is satisfied.
Predicates¶
| Predicate | Status | Notes |
|---|---|---|
allowedRegions |
Supported | Administrator assertion; empty list matches nothing |
nodeTenantAssignment: sameTenant |
Supported | Administrator assertion; needs resolved identity |
gpu.timeSlicing: forbidden |
Supported | Known true → deny; unknown → indeterminate |
gpu.mps: forbidden |
Supported | As above |
gpu.allowedPartitioning |
Supported | A separate dimension from time-slicing |
gpu.approvedDriverVersions |
Supported | Version conformity only, not vuln-free |
evidence.maxAgeSeconds / maxClockSkewSeconds |
Supported | Freshness + clock skew |
gpu.attestation: required |
Recognized, satisfiable only via an accepted verifier | With default (live) providers it is always indeterminate; enforcement blocks. See attestation |
Decision outcomes¶
For every required predicate the evaluator computes satisfied, violated, or
unresolved, then applies this algebra:
flowchart TD
START[Evaluate every required predicate] --> V{any violated?}
V -- yes --> DENY[deny<br/>exit 2]
V -- no --> U{any unresolved?}
U -- yes --> IND[indeterminate<br/>exit 3]
U -- no --> ALLOW[allow<br/>exit 0]
- deny if any required predicate is violated — unresolved findings are still preserved in the record.
- indeterminate if none is violated but at least one is unresolved.
- allow only if all required predicates are satisfied.
The enforcing adapter admits only allow. Unknown required fields,
unsupported requirements, stale sources, partial device inventory, or mismatched
node incarnations never default to allow. An invalid policy or request is a
validation error, not a valid policy decision.
Monotonicity guarantee (property-tested)
Adding a requirement or removing required evidence can never turn a
blocked result into allow. The same policy evaluated in enforce can differ
from observe only by becoming more conservative (fewer accepted sources),
never more permissive.
Effective requirements¶
Effective requirements are the conjunction of administrator policy and any allowed stricter workload requirements — a tenant can only tighten the baseline, never weaken it. Contradictory requirements produce an unsatisfiable result; they do not silently pick the more permissive value.
Reason codes¶
Each finding carries its policy field path, the expected requirement, the observed value / status, the source reference, and the candidate scope. Stable reason codes:
| Reason code | When |
|---|---|
REGION_DISALLOWED |
Region outside the allowlist |
TENANT_ASSIGNMENT_MISMATCH |
Node not assigned to the workload's tenant |
TIME_SLICING_FORBIDDEN |
Time-slicing known enabled where forbidden |
MPS_FORBIDDEN |
MPS known enabled where forbidden |
PARTITIONING_DISALLOWED |
Partitioning value outside the allowlist |
DRIVER_NOT_APPROVED |
Driver version known and not in the approved set |
EVIDENCE_STALE |
Missing timestamp, older than maxAgeSeconds, or future-dated beyond skew |
SOURCE_NOT_ACCEPTED |
Evidence source not accepted in this context (e.g. mock under enforce) |
INVENTORY_INCOMPLETE |
Partial / mixed device inventory; cannot conclude |
ATTESTATION_REQUIRED |
Attestation required but no accepted verifier result present |
ATTESTATION_VERIFIER_NOT_ACCEPTED |
Verifier id not in acceptedAttestationVerifiers (e.g. sim:) |
ATTESTATION_FAILED |
Appraised attestation outcome was fail |
REQUIREMENT_UNSUPPORTED |
Predicate recognized but unsupported in this build |
NODE_INCARNATION_MISMATCH |
Candidate node UID / incarnation ≠ snapshot |
IDENTITY_UNRESOLVED |
Tenant could not be resolved |
AUTHENTICATED_INVENTORY_MISSING |
require mode but no SVID-bound inventory for the node |
Tenant-facing messages are sanitized so they never disclose another tenant's identity or placement.
Evidence sources¶
There is no universal numeric trust score. Acceptance is claim-specific and
per-context. Every Observation<T> carries one of these source categories plus a
source identity and times:
| Source | Meaning | Accepted where |
|---|---|---|
mock |
Administrator-controlled synthetic snapshot file | Offline / observe only — rejected under enforce |
admin_inventory |
Administrator assertion (e.g. GPU-operator sharing config) | Node-authoritative config, not a hardware measurement |
host_observation |
Read-only host query (e.g. bounded nvidia-smi) |
Discovery facts; never promoted to attestation |
kubernetes_observation |
Facts observed from Kubernetes objects | Observer context |
authenticated_collector |
SVID-bound inventory from an mTLS collector | Accepted under enforce (still rejects mock) |
attestation_verifier |
Appraised result from an accepted RATS verifier | Only source that can satisfy attestation: required |
Observation status is one of known | unknown | unsupported | error. A known
boolean false is distinct from all three non-known states — "we observed it is
off" is not the same as "we don't know".
Fail-closed semantics¶
- A missing collector, stale report, or unaccepted source never yields
allow— there is no such code path. - Unavailable evidence produces
indeterminate; enforce mode then blocks the new placement. - Freshness: a known observation is stale (→
EVIDENCE_STALE) if it has no timestamp, is older thanmaxAgeSeconds, or is future-dated beyondmaxClockSkewSeconds.maxAgeSeconds: 0disables the freshness check. - Node binding: the evaluator requires
candidate.nodeUid == snapshot.nodeUid; a mismatch isNODE_INCARNATION_MISMATCH. - Policy digest is computed over a canonical JSON representation (recursively sorted keys), not raw YAML bytes. A decision expires no later than its earliest required-evidence expiry or the configured decision TTL; new policy / evidence revisions force re-evaluation sooner.
See the trust-sources ADR for the acceptance rules in full.