Pre-release — The API surface may change. Unaudited.
Callcium LogoCallcium

Policy Validation

Type mismatches, contradictions, redundancies, and vacuities.

The SDK validates policies through semantic analysis. When PolicyBuilder.build() is called, the PolicyValidator inspects every constraint for type errors, logical contradictions, redundancies, and vacuous conditions, then throws if any issue is found — regardless of severity.

Using validation

PolicyBuilder.build() validates by default and throws a CallciumError("VALIDATION_ERROR") on the first issue:

import { PolicyBuilder, arg } from "@callcium/sdk";

// Throws CallciumError("VALIDATION_ERROR") if issues are found.
const policy = PolicyBuilder
  .create("approve(address,uint256)")
  .add(arg(0).isIn(trustedSpenders))
  .build();

To inspect issues across all severities without throwing, use PolicyBuilder.validate():

const issues = PolicyBuilder
  .create("approve(address,uint256)")
  .add(arg(0).isIn(trustedSpenders))
  .validate();

for (const issue of issues) {
  // Handle each issue: severity, category, code, value1, value2, message.
}

validate() returns an Issue[] covering errors, warnings, and info-level suggestions in a single call. The builder can be inspected multiple times before committing to build().

To encode a policy despite reported issues, use buildUnsafe(). It skips validation entirely — the deliberate escape hatch for policies that target an enforcer with different operational limits than the reference Solidity enforcer:

const policy = builder.buildUnsafe();

Issue structure

Each issue is an Issue object with the following fields:

FieldTypeDescription
severity"info" | "warning" | "error"Impact level for display and triage. Any issue blocks build().
category"typeMismatch" | "contradiction" | "redundancy" | "vacuity" | "compatibility"Nature of the issue.
groupIndexnumberIndex of the group containing the issue.
constraintIndexnumberIndex of the constraint within the group.
codestringMachine-readable identifier.
value1HexFirst value involved in the issue (e.g. the lower bound in a contradiction).
value2HexSecond value involved in the issue (e.g. the upper bound).
messagestringHuman-readable description suitable for logs.

value1 and value2 surface the conflicting operands behind contradictions and redundancies, which makes validation output rich enough to render diagnostic UI: highlight the two bounds of an impossible between, the two values of eq(x) and neq(x), or the overlap between two set constraints.

Both values are Hex strings in the canonical 32-byte-padded encoding used throughout the wire format. Convert with BigInt(value1) for numeric comparisons, or slice as needed for addresses (0x + last 40 hex chars) and bytes32 values.

Severity levels

Severity is a presentation and triage signal, not a gating mechanism: build() requires a clean validation result and throws on any issue. buildUnsafe() bypasses validation for all of them.

  • error: the policy is structurally or semantically broken (type mismatch, impossible constraint).
  • warning: the policy is valid but suspicious (e.g. a redundant constraint that is always implied by another).
  • info: an observation that may suggest a clearer formulation.

The strict gate is identical across implementations: the Solidity PolicyBuilder.build() reverts under the same zero-issue rule, and both validators are pinned to each other by the shared conformance vector suite.

Categories

CategoryMeaning
typeMismatchAn operator was applied to an argument type it does not support.
contradictionTwo constraints in the same group cannot both hold (e.g. eq(x) and neq(x)).
redundancyA constraint is implied by another in the same group and adds no additional check.
vacuityA constraint is satisfied by every value (e.g. between(0, 2**256 - 1) on a uint256).
compatibilityA construct exceeds the reference enforcer's operational limits, or is a known semantic footgun (e.g. neq under any()).

Skipping validation

PolicyBuilder does not expose an unsafe build path; every blob it emits has been validated. To construct a policy without running the validator (for tooling that replays opaque blobs or round-trips decoded data), build a PolicyData object directly and pass it to PolicyCoder.encode. This is an advanced path; prefer PolicyBuilder for authored policies.

import { PolicyCoder } from "@callcium/sdk";
import type { PolicyData } from "@callcium/sdk";

const data: PolicyData = {
  /* ... fully populated canonical structure ... */
};
const policy = PolicyCoder.encode(data);

See Policy Inspection for the shape of PolicyData.

On this page