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:

  1. Be deterministic. Given the same manifest and the same external signals, evaluation produces the same decision every time.
  2. Be side-effect-free during evaluation. Predicates may read state; they MUST NOT write state.
  3. 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.
  4. Be versionable. Every policy carries a cryptographic version hash that appears in receipts.
  5. 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:

  1. An optional policy header block.
  2. Zero or more rule statements.
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.

EffectMeaning
ALLOWThe action proceeds without further intervention.
DENYThe action is rejected. The agent SHALL NOT execute it.
ESCALATEThe action is queued for human review. It does not execute until a human approves.
REFUSEThe action is rejected AND a security incident is created. Reserved for clear policy violations (cf. DENY, which is a routine block).
PAUSEAll 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:

Array indexing supports [] (any element), [0] (specific index), and [length] (cardinality).

7.1.2 External signal paths

Three external signal namespaces are reserved:

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

PredicateReturns
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

  1. Load the policy set in document order. Compile to an intermediate representation.
  2. Resolve external signals by calling out to the operator-supplied signal provider for each approver., signal., and agent.runtime.* reference appearing in any rule. Signal values are pinned for the duration of this evaluation.
  3. Evaluate rules in document order. Each matching rule contributes its effect.
  4. Resolve conflicts per Section 5.
  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:

  1. It accepts the grammar defined in Sections 6–7.
  2. It implements conflict resolution per Section 5.
  3. It enforces a default-deny timeout per Section 10.
  4. It produces a Decision Object per Section 9, with stable policy_set_hash matching Section 8.
  5. 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

14. Privacy Considerations

15. Versioning

CPL is versioned at three levels:

  1. Language version (this spec) — major changes require RFC.
  2. Policy document version — operator-set; appears in policy.version.
  3. 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

17. References


End of VAL-CPL v0.1.