Skip to content

Constraints

Scopes name what an agent is allowed to do. Constraints describe where, when, and on which version it’s allowed to do it. The two are checked separately by the verifier — both must pass for a bundle to be considered valid.

┌─────────────────────────────────────┐
│ DelegationCert │
│ │
│ scope: [physical:move] │ ← what
│ │
│ constraints: [ │
│ { type: geo_circle, │ ← where
│ lat: 37.77, lon: -122.41, │
│ radius_m: 500 }, │
│ │
│ { type: temporal, │ ← when
│ valid_hours: [6, 22], │
│ days: [1,2,3,4,5] }, │
│ │
│ { type: version, │ ← which version
│ min: "1.2.0" } │
│ ] │
└─────────────────────────────────────┘
FamilyType valuesPurpose
Geographicgeo_circle, geo_polygonAgent can only act inside a region
TemporaltemporalAgent can only act during specified hours / days
VersionversionAgent must be running a specific version range

The constraint families are intentionally narrow. They cover the three things every adapter asked for in v1 design discussions; everything else fits in custom: scopes or application-level policy.

{
"type": "geo_circle",
"lat": 37.7749,
"lon": -122.4194,
"radius_m": 500
}

The agent must be inside a 500-meter circle around (37.7749, -122.4194). The verifier needs to know the agent’s claimed location at verification time:

const result = await verifyBundle(bundle, {
required_scope: "physical:move",
context: {
location: { lat: 37.7751, lon: -122.4190 }, // 30m from center → ✓
},
});

Or polygon for non-circular regions (e.g. a warehouse footprint):

{
"type": "geo_polygon",
"points": [
{ "lat": 37.7755, "lon": -122.4200 },
{ "lat": 37.7755, "lon": -122.4180 },
{ "lat": 37.7745, "lon": -122.4180 },
{ "lat": 37.7745, "lon": -122.4200 }
]
}

The verifier uses ray-casting for polygon-inside checks. Reference implementations in all four SDKs are byte-identical (verified by conformance fixture 47–52).

The presenter (the agent) provides location in the verification context. The verifier trusts the presenter to report its true location. This is by design: the protocol doesn’t solve “is this agent telling the truth about where it is?” — that’s a layer above, typically handled by hardware attestation (TPM/secure enclave) or out-of-band signals (Wi-Fi MAC, cell-tower triangulation, GPS time-of-flight).

If you need stronger geo assurance, the runtime context should be vouched for by something the verifier already trusts: a TPM quote, a vehicle’s CAN-bus reading, a fleet management system’s record. The constraint is then “agent claims X, system records Y, do they match?”

{
"type": "temporal",
"valid_hours": [6, 22],
"days": [1, 2, 3, 4, 5]
}

The agent can only act between 6:00 and 22:00 (in the agent’s local time, derived from the context’s now and timezone), Monday through Friday (days uses ISO 8601 day numbering; Monday=1).

const result = await verifyBundle(bundle, {
required_scope: "system:execute",
context: {
now: new Date("2026-05-11T10:30:00-08:00"), // Monday 10:30 PT → ✓
timezone: "America/Los_Angeles",
},
});

If timezone is unspecified, UTC is assumed.

  • Business hours only: valid_hours: [9, 17], days: [1,2,3,4,5]
  • No weekend access: days: [1,2,3,4,5], no valid_hours
  • Maintenance window: valid_hours: [2, 4] (2am–4am)
  • Always: no temporal constraint at all — the default
{
"type": "version",
"min": "1.2.0",
"max": "2.0.0",
"exclude": ["1.4.2", "1.4.3"]
}

The agent must be running a version that satisfies the range. SemVer rules apply (greater-than-or-equal to min, less-than max, not in exclude).

const result = await verifyBundle(bundle, {
required_scope: "vehicle:drive",
context: {
version: "1.3.5", // ≥1.2.0, <2.0.0, not excluded → ✓
},
});

Use case: revoke a fleet of agents running a known-vulnerable firmware version without revoking individual cert IDs. Just push a version-constraint update.

Interaction between scopes and constraints

Section titled “Interaction between scopes and constraints”

Both must pass for a bundle to be valid:

verify_bundle(bundle, options) returns valid iff:
1. All signature checks pass (signature)
2. Freshness check passes (challenge_at within window)
3. No cert is expired (issued_at ≤ now < expires_at)
4. Chain is well-formed (subjects = next issuers)
5. Effective scope covers required (scope intersection)
6. No cert is revoked (revocation list check)
7. All constraints pass (for each cert in chain, for each
constraint, the context satisfies it)

A constraint that the context can’t satisfy is a rejection, not a partial match. If the delegation requires geo_circle and the verification context has no location, the verifier rejects with identity_status: constraint_violation and error_reason: "location required".

Sub-delegation constraints are additive: if Alice constrains Agent-A to a geo region, and Agent-A sub-delegates to Agent-B with a different geo region, Agent-B must satisfy both.

Alice → Agent-A
constraint: geo_circle (warehouse)
Agent-A → Agent-B
constraint: temporal (6am–8am)
Agent-B presenting a proof bundle must be:
✓ Inside the warehouse (from Alice's constraint)
✓ At 6am–8am (from Agent-A's constraint)
If both pass: bundle is valid.
If either fails: identity_status = constraint_violation.

Constraints never weaken down the chain. They only accumulate.

// Go
import "github.com/identities-ai/ratify-protocol"
result := ratify.VerifyBundle(bundle, ratify.VerifyOptions{
RequiredScope: "physical:move",
Context: &ratify.VerifyContext{
Location: &ratify.GeoPoint{Lat: 37.7751, Lon: -122.4190},
Now: time.Now(),
Version: "1.3.5",
},
})
# Python
result = verify_bundle(
bundle,
required_scope="physical:move",
context=VerifyContext(
location=GeoPoint(lat=37.7751, lon=-122.4190),
now=datetime.now(),
version="1.3.5",
),
)
// Rust
let result = verify_bundle(&bundle, &VerifyOptions {
required_scope: Some("physical:move".into()),
context: Some(VerifyContext {
location: Some(GeoPoint { lat: 37.7751, lon: -122.4190 }),
now: Some(SystemTime::now()),
version: Some("1.3.5".into()),
}),
..Default::default()
})?;
// TypeScript
const result = await verifyBundle(bundle, {
required_scope: "physical:move",
context: {
location: { lat: 37.7751, lon: -122.4190 },
now: Date.now(),
version: "1.3.5",
},
});

Constraint encoding and evaluation rules are normative: SPEC.md §10. The full constraint vocabulary is frozen for v1.x; new constraint families require a minor bump.