Chio/Docs

Underwriting Risk Taxonomy

Underwriting in chio is the function that turns receipt history, reputation, certifications, and runtime attestations into a signed decision about whether an agent can hold credit and on what terms. The types and policy described here all live in chio-underwriting. Think of underwriting as a credit committee built out of evidence rather than sentiment: every input has a schema, every output has a signature, and every threshold is configurable.

Use this page when

You need the canonical risk classes, reason codes, and decision-policy thresholds. Don't use this if you want the narrative walkthrough (see the Credit & Underwriting Guide) or facility/bond accounting (see Credit Facilities).

Source of truth

This page describes the types in crates/chio-underwriting/src/lib.rs and crates/chio-underwriting/src/premium.rs in the chio repository. Names and defaults below match those files exactly.

Risk Classes

UnderwritingRiskClass tags every finding with one of four severity levels. The class is ordered: each higher level dominates the lower ones when rolling findings up to the report-level risk_class.

ClassReadingTypical posture
BaselineClean evidence, no findings against itApprove at requested ceiling
GuardedOne or more soft signals (probationary history, weaker runtime assurance than the approve target but above the step-up floor)Approve with reduced ceiling
ElevatedMaterial signals (stale or thin receipt history, low reputation below the approve threshold, missing runtime assurance)Reduce ceiling or step up for manual review
CriticalHard failures (failed or revoked certification, failed settlement exposure, reputation below the deny threshold)Deny

The taxonomy is versioned. Every signed decision artifact embeds the taxonomy version (chio.underwriting.taxonomy.v1) so consumers can audit which schema produced a given outcome.


Reason Codes

UnderwritingReasonCode is the machine-readable label attached to each input UnderwritingSignal. Thirteen codes are defined. The table below gives the trigger condition, the risk severity that signal carries when present, and the typical decision the evaluator routes it to.

CodeTriggerSeverityTypical routing
ProbationaryHistorySubject is still in the probationary window of its reputation tierGuardedReduceCeiling
LowReputationEffective score below the approve threshold (or below the deny threshold)Elevated or CriticalReduceCeiling or Deny
ImportedTrustDependencyReputation depends on signals imported from another peerGuardedReduceCeiling
MissingCertificationTool requires a certification artifact that was not foundElevatedStepUp (when policy requires certs)
FailedCertificationCertification check ran and returned a failing verdictCriticalDeny
RevokedCertificationCertification was previously valid but has been revokedCriticalDeny
MissingRuntimeAssuranceNo runtime attestation observed for governed receiptsElevatedStepUp
WeakRuntimeAssuranceRuntime assurance tier is below the approve target but above the step-up floorGuardedReduceCeiling
PendingSettlementExposureOutstanding receipts are not yet settledGuarded or ElevatedReduceCeiling
FailedSettlementExposureOne or more prior settlements failedCriticalDeny
MeteredBillingMismatchMetered usage and billed amount disagreeGuarded or ElevatedReduceCeiling
DelegatedCallChainCall originated through a delegation chain that adds riskGuardedReduceCeiling
SharedEvidenceProofRequiredEvidence references shared sources that need additional proofGuardedReduceCeiling

These reason codes are the input vocabulary. The evaluator translates them into UnderwritingDecisionReasonCode values on findings (for example, PolicySignal, ReputationBelowApproveThreshold, RuntimeAssuranceBelowStepUpTier), which carry the original signal reason on the finding so consumers can trace the decision back to its evidence.


Decision Outcomes

UnderwritingDecisionOutcome has four ordered variants. The report-level outcome is the maximum of every finding's outcome, so any single deny finding wins.

OutcomeMeaningMaps to UnderwritingReviewStateMaps to UnderwritingBudgetAction
ApproveGrant the requested ceiling at full strengthApprovedPreserve
ReduceCeilingGrant a narrower ceiling, multiplied by reduce_ceiling_factorApprovedReduce
StepUpHold pending manual review or stronger evidenceManualReviewRequiredHold
DenyReject; no ceiling is grantedDeniedDeny

Default Policy Thresholds

UnderwritingDecisionPolicy::default() ships with the values below. The policy is signed alongside the decision report so the thresholds in force at decision time are recoverable from the artifact.

chio-underwriting/src/lib.rs
pub struct UnderwritingDecisionPolicy {
    pub schema: String,
    pub version: String,
    pub minimum_receipt_history: u64,
    pub maximum_receipt_age_seconds: u64,
    pub minimum_approve_reputation_score: f64,
    pub deny_reputation_score_below: f64,
    pub minimum_step_up_runtime_assurance_tier: RuntimeAssuranceTier,
    pub minimum_approve_runtime_assurance_tier: RuntimeAssuranceTier,
    pub require_active_tool_certification: bool,
    pub require_compliance_score_reference: bool,
    pub reduce_ceiling_factor: f64,
}

// Default values:
// minimum_receipt_history = 1
// maximum_receipt_age_seconds = 60 * 60 * 24 * 30 = 30 days
// minimum_approve_reputation_score = 0.6
// deny_reputation_score_below = 0.25
// minimum_step_up_runtime_assurance_tier = RuntimeAssuranceTier::Attested
// minimum_approve_runtime_assurance_tier = RuntimeAssuranceTier::Verified
// require_active_tool_certification = true
// require_compliance_score_reference = false
// reduce_ceiling_factor = 0.5
Reputation scoreRouted to
score >= 0.6Approve (subject to other findings)
0.25 <= score < 0.6ReduceCeiling on a ReputationBelowApproveThreshold finding (manual review when combined with other elevated signals)
score < 0.25Deny on a ReputationBelowDenyThreshold finding

policy.validate() rejects configurations that put the deny floor at or above the approve threshold, so the manual review band cannot collapse to zero width.

The same banded view is the spine for risk classification: the approve and deny cuts on the underwriting policy line up with the scorecard bands the report ultimately resolves to.

Credit scorecard bandsDimensions feeding the scorePrime : high score, full settlement discipline, no loss pressure; largest facility ceilingsPrimestrongest tier | auto ApproveStandard : healthy default tier; normal facility termsStandardhealthy default | auto ApproveGuarded : mild signals of stress (slow settlement, mixed-currency book, modest loss pressure)Guardedmild stress signals | ReduceCeilingProbationary : sparse history, sparse day coverage, or low confidence; conservative ceilingsProbationarysparse history or low confidence | StepUp / manual reviewRestricted : persistent failed settlements, outstanding provisional losses, or low reputation; credit generally deniedRestrictedfailed settlement or low reputation | DenyReputationSupport : the composite reputation score for the subjectReputationSupportcomposite reputation, attenuated by importsSettlementDiscipline : pending vs failed settlement counts on receiptsSettlementDisciplinepending vs failed settlement countsLossPressure : outstanding provisional loss amounts not yet recoveredLossPressureoutstanding provisional lossExposureStewardship : concentration, currency mix, and reserve-aware utilization patternsExposureStewardshipconcentration, currency mix, reservesfeedsfeedsfeedsfeedsauto-approve cutscore >= 0.6 · policy thresholdmanual-review zone0.25 <= score < 0.6 · ReduceCeiling or StepUpauto-deny cutscore < 0.25 · policy floordecision cuts · configurable in the underwriting policybands · most permissive on topdimensions feeding the composite scoreconfidence (Low / Medium / High) is an orthogonal axissample size determines confidence · confidence in turn caps the banddashed = dimension feeds composite score
Credit scorecard bands. Bands map score ranges to default dispositions; the 4 dimensions on the right feed the composite score. Auto-approve and auto-deny cuts are configurable in the underwriting policy.

Lookback windows

Receipt evidence freshness is governed by maximum_receipt_age_seconds on the policy (default 30 days). A separate lookback window is used by price_premium for premium pricing, expressed as a LookbackWindow{ since, until } and recorded on the resulting PremiumQuote for audit.

Evidence Kinds and Compliance Bundle

Findings reference UnderwritingEvidenceReference records, each tagged with an UnderwritingEvidenceKind:

chio-underwriting/src/lib.rs
pub enum UnderwritingEvidenceKind {
    Receipt,
    ReputationInspection,
    CertificationArtifact,
    RuntimeAssuranceEvidence,
    SettlementReconciliation,
    MeteredBillingReconciliation,
    SharedEvidenceReference,
}

These kinds let consumers route evidence back to the system that produced it. A RuntimeAssuranceEvidence reference, for example, includes a SHA-256 digest and a verifier locator pointing at the attestation that justified the finding.

UnderwritingComplianceEvidence

When the optional compliance score is included in the input bundle, it ships in this fixed shape:

chio-underwriting/src/lib.rs
pub struct UnderwritingComplianceEvidence {
    pub schema: String,
    pub agent_id: String,
    pub score: u32,                          // 0..=1000
    pub generated_at: u64,                   // unix seconds
    pub total_receipts: u64,
    pub deny_receipts: u64,
    pub observed_capabilities: u64,
    pub revoked_capabilities: u64,
    pub attestation_age_secs: Option<u64>,   // None when no attestation observed
}

When require_compliance_score_reference is set on the policy and this field is None, the evaluator emits a ComplianceScoreRequired finding that routes to StepUp.


Premium Pricing

Two premium surfaces ship with chio-underwriting: the decision-time premium quoted on the signed artifact, and the standalone price_premium function used by chio-market to quote-and-bind an insurance policy.

Decision-Time Premium

Each signed UnderwritingDecisionArtifact carries an UnderwritingPremiumQuote:

chio-underwriting/src/lib.rs
pub enum UnderwritingPremiumState {
    Quoted,
    Withheld,
    NotApplicable,
}

pub struct UnderwritingPremiumQuote {
    pub state: UnderwritingPremiumState,
    pub basis_points: Option<u32>,
    pub quoted_amount: Option<MonetaryAmount>,
    pub rationale: String,
}

Basis points (a basis point is 1/100 of a percent, so 100 bps = 1%) are a fixed schedule keyed off the outcome and the rolled-up risk class:

OutcomeBaselineGuardedElevatedCritical
Approve100 bps150 bps200 bps300 bps
ReduceCeiling150 bps250 bps400 bps600 bps
StepUpWithheld (no quote until manual review or stronger evidence completes)
DenyNotApplicable

The quoted amount equals ceil(exposure_units * bps / 10_000) in the same currency as the quoted exposure. When no exposure is supplied, only the basis points appear on the quote.

Insurance-Flow Premium

The standalone price_premium function takes a compliance score in 0..=1000 plus an optional behavioral z-score and produces a PremiumQuote. It is deterministic and fail-closed: a missing compliance score returns a decline rather than a silent approval.

chio-underwriting/src/premium.rs
Score band | Multiplier | Disposition
-----------|-----------:|------------
> 900      |       1.0  | Quoted
700..=900  |       2.0  | Quoted
500..700   |       5.0  | Quoted
< 500      |        -   | Declined

quoted_cents = base_rate_cents * (1 + risk_multiplier(score))

Behavioral anomalies erode the score through a per-sigma penalty before the band is chosen. Defaults: penalty per sigma above the threshold is 50 points, capped at 250 points total, with the threshold at 3 sigma. The quote's justification string records the inputs used for audit.

See Liability Market for how this quote is bound into a policy via quote_and_bind.


Worked Example

Take an agent with two recent governed receipts, a Verified runtime assurance tier, and a reputation score of 0.4. The agent is still in its probationary window. The input carries one signal: ProbationaryHistory at Guarded severity.

example.rs
UnderwritingPolicyInput {
    schema: "chio.underwriting.policy-input.v1",
    generated_at: 1_700_000_000,
    filters: UnderwritingPolicyInputQuery {
        agent_subject: Some("agent-42".into()),
        receipt_limit: Some(100),
        ..default()
    },
    taxonomy: UnderwritingRiskTaxonomy::default(),
    receipts: UnderwritingReceiptEvidence {
        matching_receipts: 2,
        // ... two recent governed receipts ...
    },
    reputation: Some(UnderwritingReputationEvidence {
        subject_key: "agent-42".into(),
        effective_score: 0.4,
        probationary: true,
        ..
    }),
    runtime_assurance: Some(UnderwritingRuntimeAssuranceEvidence {
        highest_tier: Some(RuntimeAssuranceTier::Verified),
        ..
    }),
    signals: vec![UnderwritingSignal {
        class: UnderwritingRiskClass::Guarded,
        reason: UnderwritingReasonCode::ProbationaryHistory,
        description: "subject is still probationary".into(),
        evidence_refs: vec![/* ... */],
    }],
    ..default()
}

The evaluator produces two findings:

  • A reputation finding: score 0.4 is below the approve threshold of 0.6 and above the deny floor of 0.25, so it routes to ReduceCeiling at Elevated class with reason ReputationBelowApproveThreshold and signal_reason LowReputation.
  • A signal-driven finding for ProbationaryHistory: routes to ReduceCeiling at Guarded class with reason PolicySignal.

Rolled up: report outcome is ReduceCeiling (the maximum of the two), risk class is Elevated (the maximum of the two), and suggested_ceiling_factor is 0.5 from the default policy. The signed artifact carries:

  • review_state: Approved
  • budget: Reduce with ceiling factor 0.5
  • premium: Quoted at 400 bps (ReduceCeiling at Elevated). Against a $1,000 exposure, the quoted premium is ceil(1000 * 400 / 10_000) = 40 cents per the same currency.

Worked Decision Example

Take a concrete agent: reputation 0.42, eight governed receipts in the last 30 days, no tool-server certification on file. The operator wants to size a 1,000-unit ceiling against this agent. Walk through what the evaluator emits.

Reputation Evidence Shape

The reputation block on UnderwritingPolicyInput is fixed:

chio-underwriting/src/lib.rs
pub struct UnderwritingReputationEvidence {
    pub subject_key: String,
    pub effective_score: f64,
    pub probationary: bool,
    pub resolved_tier: Option<String>,
    pub imported_signal_count: usize,
    pub accepted_imported_signal_count: usize,
}

For our agent this resolves to:

example-input.json
{
  "subjectKey": "agent-vanguard-soc2",
  "effectiveScore": 0.42,
  "probationary": false,
  "resolvedTier": "tier-2",
  "importedSignalCount": 0,
  "acceptedImportedSignalCount": 0
}

Triggers

Two thresholds fire on this input:

  • LowReputation: 0.42 is below the approve threshold of 0.6 and above the deny floor of 0.25, so it routes to a ReputationBelowApproveThreshold finding at Elevated class.
  • MissingCertification: the policy default require_active_tool_certification is true, and no certification artifact resolves for the tool server. The finding routes at Elevated class with reason PolicySignal.

Outcome

Both findings carry ReduceCeiling as their outcome (eight receipts is enough history; the runtime assurance tier is high enough to clear step-up). Rolled up: report outcome is ReduceCeiling, risk class is Elevated, and the finding carries reason codes [LowReputation, MissingCertification] on its signal_reasons vector for traceability.

Premium Calculation

ReduceCeiling at Elevated is priced at 250 bps (2.5%) per the decision-time schedule. The amount comes from the actual chio-underwriting helper:

chio-underwriting/src/lib.rs
fn quote_premium_amount(exposure: &MonetaryAmount, basis_points: u32) -> MonetaryAmount {
    let units = (u128::from(exposure.units) * u128::from(basis_points)).div_ceil(10_000_u128);
    MonetaryAmount {
        units: units as u64,
        currency: exposure.currency.clone(),
    }
}

Apply it: units = ceil(1000 * 250 / 10_000) = 25. The signed UnderwritingDecisionArtifact carries an UnderwritingPremiumQuote with basis_points = 250 and quoted_amount = MonetaryAmount{ units: 25, currency: "USD" }.

CLI Invocation

The evaluation runs through chio underwriting evaluate:

terminal
$ chio underwriting evaluate --agent agent-vanguard-soc2
decision_id:        uw-dec-9c3f2a18
agent_subject:      agent-vanguard-soc2
outcome:            reduce_ceiling
risk_class:         elevated
review_state:       approved
budget:             reduce (factor=0.5)
suggested_ceiling:  500 USD (from requested 1000)
findings:
  - reason: reputation_below_approve_threshold
    class: elevated
    signal_reasons: [low_reputation]
    description: effective score 0.42 below approve threshold 0.6
  - reason: policy_signal
    class: elevated
    signal_reasons: [missing_certification]
    description: tool server certification not resolvable
premium:
  state:        quoted
  basis_points: 250
  amount:       25 USD
  rationale:    reduce_ceiling at elevated risk
taxonomy:           chio.underwriting.taxonomy.v1
policy_version:     chio.underwriting.policy.v1

The same call, with --export-signed, emits the signed artifact for downstream consumers (chio-credit, chio-market) to verify against the kernel signing key.


Appeals

Decisions can be challenged via an UnderwritingAppealRecord. Appeals carry a status of Open, Accepted, or Rejected. An accepted appeal can reference a replacement_decision_id; creating that replacement supersedes the original decision (lifecycle state moves from Active to Superseded).


Next Steps