Skip to main content

Policies

Reference for the declarative manifests that define Catalyst policies. For the conceptual story see Policies. For end-to-end usage see Resiliency and IDs.

Policies are applied with the diagrid CLI. The target project is provided by the --project flag, not by a field in the manifest:

diagrid resiliency create -f resiliency.yaml --project my-project
diagrid configuration create -f configuration.yaml --project my-project
diagrid workflow access-policy create -f workflow-access-policy.yaml --project my-project

Manifest envelope

Every policy manifest shares the same top-level shape:

apiVersion: dapr.io/v1alpha1
kind: <Resiliency | Configuration | WorkflowAccessPolicy>
metadata:
name: <policy-name>
scopes:
- <app-id-1>
- <app-id-2>
spec:
# ...kind-specific spec below
FieldTypeRequiredDescription
apiVersionstringyesMust be dapr.io/v1alpha1.
kindstringyesResiliency, Configuration, or WorkflowAccessPolicy.
metadata.namestringyesUnique policy name within the project.
scopesstring[]noWorkloads the policy applies to. Top-level (sibling of spec). Omit to leave the policy unbound — except for WorkflowAccessPolicy, where omitting scopes applies the policy to every app in the project.

Resiliency policy

A resiliency policy defines named retry, timeout, and circuit-breaker behaviours, then binds them to outbound calls by target.

Spec

spec:
policies:
timeouts:
<policy-name>: <duration>
retries:
<policy-name>:
policy: constant | exponential
duration: <duration>
maxInterval: <duration> # exponential only
maxRetries: <int> # -1 for unlimited
matching:
httpStatusCodes: "<comma-separated codes or ranges>"
gRPCStatusCodes: "<comma-separated codes>"
circuitBreakers:
<policy-name>:
maxRequests: <int>
interval: <duration>
timeout: <duration>
trip: "<CEL expression>"
targets:
apps:
<app-id>:
timeout: <timeout-policy-name>
retry: <retry-policy-name>
circuitBreaker: <circuit-breaker-policy-name>
circuitBreakerCacheSize: <int>
components:
<component-name>:
inbound:
timeout: <timeout-policy-name>
retry: <retry-policy-name>
circuitBreaker: <circuit-breaker-policy-name>
outbound:
timeout: <timeout-policy-name>
retry: <retry-policy-name>
circuitBreaker: <circuit-breaker-policy-name>

Retry policy fields

FieldTypeDescription
policyconstant | exponentialBackoff shape. Constant waits duration between attempts; exponential grows the wait up to maxInterval.
durationduration string (e.g. 5s)Wait between attempts (constant) or initial wait (exponential).
maxIntervalduration stringUpper bound on the wait for exponential policies. Ignored for constant.
maxRetriesintMaximum attempts after the initial call. -1 means retry indefinitely.
matching.httpStatusCodesstringComma-separated HTTP codes or ranges to retry, e.g. "500,501,502,503,504". Unmatched codes fail immediately.
matching.gRPCStatusCodesstringComma-separated gRPC status codes to retry, e.g. "14,4,8".

Circuit-breaker fields

FieldTypeDescription
maxRequestsintNumber of probe requests permitted while the breaker is half-open.
intervalduration stringSliding window over which failures are counted.
timeoutduration stringTime the breaker stays open before transitioning to half-open.
tripCEL expressionTrip condition. Evaluated against the variables below, e.g. "consecutiveFailures > 5".

The trip expression is evaluated as CEL with the following variables in scope:

VariableDescription
requestsTotal requests observed in the current window.
consecutiveFailuresConsecutive failures since the last success.
consecutiveSuccessesConsecutive successes since the last failure.
totalFailuresTotal failures in the current window.
totalSuccessesTotal successes in the current window.

Target fields

apps.<app-id> binds policies to outbound service invocations to that app. components.<component-name>.outbound binds policies to calls out to the component; inbound binds policies to deliveries from the component (for example pub/sub message delivery into the app).

Each target slot accepts the name of a policy defined in policies.timeouts, policies.retries, or policies.circuitBreakers. Bindings are optional; omit a slot to leave that dimension unbounded for the target.

circuitBreakerCacheSize (apps only) caps the number of per-instance breakers cached for that target.

Defaults

If you omit a dimension, Catalyst applies these defaults at the data plane:

DimensionDefault value
Timeout10s
Retryconstant, 5s between attempts, 5 retries, matching HTTP 500,501,502,503,504 and gRPC 14,4,8
Circuit breakermaxRequests: 1, interval: 5s, timeout: 20s, trip: consecutiveFailures > 5

Worked example

apiVersion: dapr.io/v1alpha1
kind: Resiliency
metadata:
name: order-service-resiliency
scopes:
- order-service
spec:
policies:
timeouts:
fastCall: 2s
retries:
transientRetry:
policy: exponential
duration: 200ms
maxInterval: 5s
maxRetries: 4
matching:
httpStatusCodes: "503,504"
gRPCStatusCodes: "14"
circuitBreakers:
defaultBreaker:
maxRequests: 1
interval: 30s
timeout: 60s
trip: "consecutiveFailures > 5"
targets:
apps:
inventory-service:
timeout: fastCall
retry: transientRetry
circuitBreaker: defaultBreaker
components:
orders-statestore:
outbound:
timeout: fastCall
retry: transientRetry
circuitBreaker: defaultBreaker

Configuration

A configuration sets app-level runtime behaviour: access control lists, HTTP pipelines, distributed tracing (private regions only), and workflow runtime parameters. For the access-control rules model and the CLI commands to manage them, see Secure service invocation with access control.

Spec

spec:
accessControl:
defaultAction: allow | deny
trustDomain: <trust-domain> # ignored: set by the platform
policies:
- appId: <caller-app-id>
defaultAction: allow | deny
trustDomain: <trust-domain>
namespace: <namespace> # ignored: set by the platform
operations:
- name: <operation-name>
httpVerb: ["GET", "POST", ...]
action: allow | deny
appHttpPipeline:
handlers:
- name: <middleware-name>
type: <middleware-type>
httpPipeline:
handlers:
- name: <middleware-name>
type: <middleware-type>
tracing: # private regions only
samplingRate: "<float-as-string>"
otel:
endpointAddress: <url>
isSecure: <bool>
protocol: <http | grpc>
timeout: <duration>
headers:
- name: <header-name>
value: <header-value>
- name: <header-name>
secretKeyRef:
name: <secret-name>
key: <field-in-secret>
workflow:
maxConcurrentWorkflowInvocations: <int> # private regions only
maxConcurrentActivityInvocations: <int> # private regions only
globalMaxConcurrentWorkflowInvocations: <int>
globalMaxConcurrentActivityInvocations: <int>
workflowConcurrencyLimits:
- name: <workflow-name>
maxConcurrent: <int>
activityConcurrencyLimits:
- name: <activity-name>
maxConcurrent: <int>
stateRetentionPolicy:
completed: <duration>
failed: <duration>
terminated: <duration>
anyTerminal: <duration>

Field reference

FieldNotes
accessControlService-invocation ACLs. The top-level defaultAction applies when no per-caller policy matches. Per-caller policies[] can refine to specific operations and HTTP verbs. defaultAction and action accept only allow or deny; httpVerb accepts only the nine standard HTTP methods; a policies[] entry must set appId and every operations[] entry must set name.
accessControl.trustDomainTop-level only. Set by the platform: a value you supply is accepted and then discarded, on create and on update alike.
accessControl.policies[].trustDomainYours to set, and kept. Leave it out and it defaults to your organization's trust domain, which is what you want unless you are naming a caller from somewhere else.
accessControl.policies[].namespaceSet by the platform. Whatever you supply is replaced with your project's namespace, prj-<project-namespace>, because that is the namespace the access-control decision is actually made against.
appHttpPipelineMiddleware applied to traffic from the data plane to your application (inbound to the app).
httpPipelineMiddleware applied to traffic from your application out through the data plane (outbound).
tracingDistributed tracing config. Only honored on projects in private regions. samplingRate is a string between "0" and "1".
tracing.otel.headersHeaders added to every OTLP exporter request. Give each entry a name and then one of value (plaintext), secretKeyRef (a name/key pair naming a secret and the field in it), or envRef (an environment variable to read the value from).
tracing.otel.timeoutDuration string bounding each OTLP exporter request.
workflow.maxConcurrentWorkflowInvocations
workflow.maxConcurrentActivityInvocations
Per-sidecar ceilings: the most a single Dapr instance schedules at once. Invocations beyond the ceiling queue rather than fail. Omit for no limit. Honored in private regions only — see the note below.
workflow.globalMaxConcurrentWorkflowInvocations
workflow.globalMaxConcurrentActivityInvocations
The same ceilings across every replica, enforced by the scheduler rather than per sidecar. Omit for no limit.
workflow.workflowConcurrencyLimits
workflow.activityConcurrencyLimits
Per-name ceilings, enforced across every replica by the scheduler. Each entry takes a name (the workflow or activity name to limit) and a maxConcurrent. Names must be non-empty and unique within a list, and maxConcurrent must be non-negative.
workflow.stateRetentionPolicyHow long a workflow instance's state is kept after it reaches a terminal state, after which it is purged automatically. Set completed, failed or terminated per state, or anyTerminal for all of them; a specific state wins over anyTerminal. Values are duration strings such as 72h or 30m, including 0s to purge immediately. Omit the whole block and instances are never purged automatically.
warning

Some of this spec is accepted and then overwritten, with no error to tell you so. accessControl.trustDomain is always cleared and accessControl.policies[].namespace is always replaced, because the platform owns both. On a project in a public region, workflow.maxConcurrentWorkflowInvocations and workflow.maxConcurrentActivityInvocations are cleared too — read them back with diagrid configuration get and they are gone. Nothing else in workflow is affected: the global... ceilings, the per-name limits and stateRetentionPolicy are kept in every region. To bound per-sidecar concurrency in a public region, use the per-name limits instead.

Worked example

apiVersion: dapr.io/v1alpha1
kind: Configuration
metadata:
name: order-service-config
spec:
accessControl:
defaultAction: deny
policies:
- appId: api-gateway
defaultAction: deny
operations:
- name: /orders
httpVerb: ["POST"]
action: allow
workflow:
workflowConcurrencyLimits:
- name: OrderProcess
maxConcurrent: 20
activityConcurrencyLimits:
- name: ChargePayment
maxConcurrent: 5
stateRetentionPolicy:
completed: 24h
anyTerminal: 72h

The top-level accessControl.trustDomain and the per-caller namespace are left out because the platform sets both, and policies[].trustDomain is left out so it defaults to your organization's. The limits and the retention policy above are honored in every region; add maxConcurrentWorkflowInvocations and maxConcurrentActivityInvocations only for a project in a private region, where they are not dropped.

Workflow access policy

A workflow access policy is an allow-list that controls which caller apps can schedule workflows and activities on the apps in scopes. Without a policy, all cross-app workflow calls are allowed; once a policy covers an app, every cross-app call must match a rule or it is denied. Self-calls are always allowed. For the conceptual story and CLI walkthrough see Secure workflows with access policies.

Spec

spec:
rules:
- callers:
- appID: <caller-app-id>
workflows:
- name: <workflow-name or glob>
operations: [schedule]
activities:
- name: <activity-name or glob>

Field reference

FieldTypeRequiredDescription
ruleslistnoAllow-list of rules; a call is permitted if any rule matches. An empty or omitted list denies every cross-app workflow call to the scoped apps.
rules[].callerslistyesCaller apps this rule grants access to. At least one entry.
rules[].callers[].appIDstringyesThe ID of the calling application.
rules[].workflowslistno*Workflow rules granted to the matched callers.
rules[].workflows[].namestringyesExact workflow name or glob pattern (*, ?, [abc]).
rules[].workflows[].operationsstring[]yesSet to [schedule]. The API also accepts terminate, raise, pause, resume, purge, get, and rerun for forward compatibility, but these operations don't route cross-app yet, so policies have no effect on them.
rules[].activitieslistno*Activity rules granted to the matched callers. Activities only support scheduling, so there is no operations field.
rules[].activities[].namestringyesExact activity name or glob pattern.

* At least one of workflows or activities must be present in each rule.

Worked example

apiVersion: dapr.io/v1alpha1
kind: WorkflowAccessPolicy
metadata:
name: order-app-policy
scopes:
- order-app
spec:
rules:
- callers:
- appID: orchestrator-app
workflows:
- name: order-processing
operations: [schedule]
- name: "report-*"
operations: [schedule]
activities:
- name: charge-payment

Applying and inspecting policies

# Apply or update
diagrid resiliency create -f resiliency.yaml --project <project>
diagrid resiliency update -f resiliency.yaml --project <project>
diagrid configuration create -f configuration.yaml --project <project>
diagrid configuration update <name> -f configuration.yaml --project <project>
diagrid apply -f configuration.yaml --project <project>
diagrid workflow access-policy create -f workflow-access-policy.yaml --project <project>
diagrid workflow access-policy update -f workflow-access-policy.yaml --project <project>

# Inspect
diagrid resiliency list --project <project>
diagrid resiliency get <name> --project <project>
diagrid configuration list --project <project>
diagrid configuration get <name> --project <project>
diagrid workflow access-policy list --project <project>
diagrid workflow access-policy get <name> --project <project>

# Remove
diagrid resiliency delete <name> --project <project>
diagrid configuration delete <name> --project <project>
diagrid workflow access-policy delete <name> --project <project>

A configuration can also be edited without a manifest. diagrid configuration create and diagrid configuration update take the same set of inline flags — --default-action, --policy, the --tracing-* flags, the --workflow-* and --activity-concurrency-limit flags, and the pipeline-handler flags — as an alternative to --file. On update, those flags merge section by section: a section is touched only when at least one of its flags is set, and fields you leave out keep their current value. To replace a configuration wholesale instead, pass --file or use diagrid apply -f.

See the diagrid configuration CLI reference for its full flag set, and run diagrid resiliency --help for the resiliency flags.

See also