VAL-CPL — Conduct Policy Language Specification
Version: 0.1 Status: Draft License: Apache License 2.0 Editor: Open VAL Foundation (pending donation; currently VAL Inc.)
1. Abstract
This specification defines the Conduct Policy Language (CPL) — a declarative rule language for evaluating whether a given VAL-AM Action Manifest should be allowed, denied, escalated, refused, or paused. CPL is substrate-agnostic, industry-agnostic, and designed to be both human-writable and machine-evaluable.
2. Goals
A CPL policy MUST:
- Be deterministic. Given the same manifest and the same external signals, evaluation produces the same decision every time.
- Be side-effect-free during evaluation. Predicates may read state; they MUST NOT write state.
- Be bounded in execution time. A policy that does not terminate within a per-implementation deadline (default: 250 ms) SHALL be treated as a default-deny.
- Be versionable. Every policy carries a cryptographic version hash that appears in receipts.
- Be composable. Operators MAY combine multiple policy files into a policy set; the language defines deterministic conflict resolution.
3. Document Form
A CPL policy is a UTF-8 text document. The recommended file extension is .cpl. Each policy document contains:
- An optional
policyheader block. - Zero or more
rulestatements.
policy {
name: "acme-refund-controls"
version: "2026.06.07-1"
description: "Refund issuance controls for support agents"
owner: "urn:val:operator:acme.example"
}
rule "refund-cap" {
effect: ESCALATE
when: action.intent == "financial_transaction"
AND action.value_at_risk.amount > 1000
AND action.value_at_risk.unit == "USD"
unless: approver.role IN ["support_lead", "finance"]
metadata: { priority: 100, owner: "finance@acme.example" }
}
4. Decision Effects
CPL defines exactly five effects. A rule that matches contributes its effect to the policy decision.
| Effect | Meaning |
|---|---|
ALLOW | The action proceeds without further intervention. |
DENY | The action is rejected. The agent SHALL NOT execute it. |
ESCALATE | The action is queued for human review. It does not execute until a human approves. |
REFUSE | The action is rejected AND a security incident is created. Reserved for clear policy violations (cf. DENY, which is a routine block). |
PAUSE | All matching actions are halted indefinitely until explicitly resumed. Used for incident response. |
5. Conflict Resolution
When multiple rules across a policy set match a single manifest, conflicts resolve by the following precedence (most restrictive wins):
PAUSE > REFUSE > DENY > ESCALATE > ALLOW
If two rules at the same precedence level disagree (impossible for PAUSE/REFUSE/DENY since they all reject; possible only between ALLOW and ESCALATE when separated by precedence), ESCALATE wins over ALLOW. The principle is: safety dominates permissiveness.
When two rules of identical effect both match, both are recorded in the receipt, but only one effect is applied.
6. Rule Syntax
rule "<name>" {
effect: <ALLOW | DENY | ESCALATE | REFUSE | PAUSE>
when: <predicate-expression>
unless: <predicate-expression> # optional
metadata: { <key>: <value>, ... } # optional
}
A rule MATCHES a manifest when:
when-predicate evaluates to TRUE
AND (unless-predicate is absent OR evaluates to FALSE)
A matching rule contributes its effect to the decision per Section 5.
7. Predicate Expression Grammar
7.1 Atoms
Atoms are paths into a known context, comparisons against literals, or built-in predicate calls.
7.1.1 Manifest paths
Any field in the VAL-AM manifest is accessible via dotted path:
action.intentaction.value_at_risk.amountaction.targets[].countaction.side_effectagent.idagent.substrate.vendoragent.operatorcontext.originating_humantags
Array indexing supports [] (any element), [0] (specific index), and [length] (cardinality).
7.1.2 External signal paths
Three external signal namespaces are reserved:
approver.*— the identity and role of any pre-attached approversignal.*— operator-defined external signals (e.g.,signal.incident_response_active,signal.fraud_score)agent.runtime.*— runtime agent statistics (e.g.,agent.runtime.anomaly_score,agent.runtime.recent_deny_rate)
Implementations MUST provide a hook for the operator to supply external signal values at policy-evaluation time.
7.1.3 Comparison operators
== != < <= > >=
7.1.4 Membership
<expr> IN [<literal>, <literal>, ...]
<expr> CONTAINS <literal> # for strings / arrays
<expr> MATCHES /<regex>/ # PCRE-compatible regex
7.1.5 Type tests
<expr> IS NULL
<expr> IS NOT NULL
<expr> IS <type> # type ∈ {string, integer, number, boolean, array, object}
7.1.6 Built-in predicates
| Predicate | Returns |
|---|---|
targets_count() | sum of count field over action.targets, or array length if count unset |
is_business_hours(timezone, schedule) | boolean |
recent_action_count(agent_id, intent, window_seconds) | integer; rate-limiting primitive |
parent_has_intent(intent_name) | boolean; walks context.parent_action_id chain |
Implementations MAY expose additional predicates in operator namespaces (e.g., fn.acme.is_blocked_recipient(email)).
7.2 Logical composition
AND OR NOT
With standard precedence: NOT > AND > OR. Parentheses override precedence.
7.3 Examples
action.intent == "financial_transaction"
AND action.value_at_risk.amount > 1000
action.intent == "send_communication"
AND targets_count() > 100
AND NOT is_business_hours("America/Los_Angeles", "mon-fri,9-17")
action.side_effect == "irreversible"
AND (agent.runtime.anomaly_score > 0.8
OR recent_action_count(agent.id, "deploy_code", 600) > 5)
8. Policy Sets
A policy set is an ordered collection of policy documents loaded by a CPL engine. Documents are loaded in the order specified by the operator. Rule names within a policy set MUST be unique. The CPL engine SHALL reject a policy set with duplicate rule names.
A policy set has a deterministic set hash:
set_hash = SHA-256(
JCS-serialize([
SHA-256(policy_document_1_bytes),
SHA-256(policy_document_2_bytes),
...
])
)
This hash appears in every receipt to bind the receipt to an exact policy set version.
9. Decision Object
The output of evaluating a policy set against a manifest is a Decision Object:
{
"effect": "ALLOW" | "DENY" | "ESCALATE" | "REFUSE" | "PAUSE",
"matched_rules": [
{
"name": "<rule name>",
"effect": "<effect>",
"policy_name": "<policy document name>"
},
...
],
"policy_set_hash": "<SHA-256 hex>",
"evaluation_duration_ms": <integer>,
"external_signals_used": ["<signal path>", ...]
}
The Decision Object is consumed by the VAL-RF receipt issuer and bound into the receipt.
10. Evaluation Semantics
- Load the policy set in document order. Compile to an intermediate representation.
- Resolve external signals by calling out to the operator-supplied signal provider for each
approver.,signal., andagent.runtime.*reference appearing in any rule. Signal values are pinned for the duration of this evaluation. - Evaluate rules in document order. Each matching rule contributes its effect.
- Resolve conflicts per Section 5.
- Emit the Decision Object.
Total evaluation time SHALL NOT exceed the implementation deadline (default 250 ms). If the deadline is exceeded, the engine SHALL emit a Decision Object with effect = "DENY" and a timeout flag in metadata. Default-deny on timeout is mandatory — this is a security invariant of CPL.
11. Worked Example
A full policy set for a customer support operator:
policy {
name: "acme-support-agent-conduct"
version: "2026.06.07-1"
description: "Conduct controls for Acme customer support agent fleet"
owner: "urn:val:operator:acme.example"
}
rule "block-fraud-flagged" {
effect: REFUSE
when: action.intent == "financial_transaction"
AND signal.fraud_score > 0.85
metadata: { source: "fraud-team", priority: 1000 }
}
rule "pause-during-incident" {
effect: PAUSE
when: signal.incident_response_active == true
AND action.side_effect == "irreversible"
metadata: { source: "soc", priority: 950 }
}
rule "refund-cap" {
effect: ESCALATE
when: action.intent == "financial_transaction"
AND action.value_at_risk.amount > 1000
AND action.value_at_risk.unit == "USD"
unless: approver.role IN ["support_lead", "finance"]
metadata: { source: "finance", priority: 100 }
}
rule "mass-comm-throttle" {
effect: ESCALATE
when: action.intent == "send_communication"
AND targets_count() > 100
unless: approver.role == "marketing_director"
metadata: { source: "marketing-ops", priority: 90 }
}
rule "off-hours-deploys" {
effect: ESCALATE
when: action.intent == "deploy_code"
AND NOT is_business_hours("America/Los_Angeles", "mon-fri,9-17")
metadata: { source: "platform-eng", priority: 80 }
}
rule "anomaly-shutdown" {
effect: DENY
when: agent.runtime.anomaly_score > 0.90
metadata: { source: "platform-eng", priority: 200 }
}
rule "default-allow-low-risk" {
effect: ALLOW
when: action.risk_class IN ["low", "medium"]
AND action.side_effect != "irreversible"
metadata: { source: "default-policy", priority: 0 }
}
For a $4,200 refund manifest (the example from VAL-AM Section 11), the engine matches refund-cap (ESCALATE) and default-allow-low-risk (ALLOW). Per Section 5 precedence, ESCALATE wins, and the action is routed to human review.
12. Conformance
An implementation conforms to VAL-CPL v0.1 if:
- It accepts the grammar defined in Sections 6–7.
- It implements conflict resolution per Section 5.
- It enforces a default-deny timeout per Section 10.
- It produces a Decision Object per Section 9, with stable
policy_set_hashmatching Section 8. - It passes the VAL-CPL Conformance Test Suite (published separately).
Implementations MAY support extensions (new built-in predicates, additional effects in extension namespaces), but extensions MUST be disabled by default and explicitly opted into by the operator.
13. Security Considerations
- Default-deny on timeout is a hard requirement (Section 10). Without it, an adversarial input that drives the engine into a long evaluation loop converts to a silent allow. Implementations are REQUIRED to enforce the deadline.
- External signal providers are part of the trust base. A signal provider that lies about
signal.fraud_scoredefeats the policy. Operators SHOULD authenticate signal sources and SHOULD log signal values into the Decision Object (which is already required by Section 9). - Policy authoring is a security-sensitive activity. Operators SHOULD require code-review on policy changes and SHOULD restrict who can write policies (the same way they restrict who can write IAM policies).
- Policy set hash binds receipts to a specific policy version. If a policy is changed, future receipts reference a new hash. Auditors verify hashes against the historical policy archive.
14. Privacy Considerations
- Policies SHOULD NOT include PII in
metadatafields. Metadata appears in receipts and may be retained longer than the action data itself. - The
external_signals_usedfield in the Decision Object reveals which signals were referenced; operators concerned about signal-name leakage MAY hash signal names before storage in receipts.
15. Versioning
CPL is versioned at three levels:
- Language version (this spec) — major changes require RFC.
- Policy document version — operator-set; appears in
policy.version. - Policy set hash — computed; appears in every receipt.
A receipt's policy_set_hash uniquely identifies which policies were in force at the moment the action was decided.
16. Change Log
- v0.1 (2026): Initial publication.
17. References
- [RFC 2119] Key Words for Use in RFCs
- [RFC 8785] JSON Canonicalization Scheme (JCS)
- VAL-AM — Action Manifest (this repository)
- VAL-RF — Receipt Format (this repository)
End of VAL-CPL v0.1.